{
  "openapi": "3.0.3",
  "info": {
    "title": "Mailpro™ CRM API v3",
    "version": "3",
    "description": "La API CRM v3 de Mailpro™ le permite gestionar mediante programación contactos, listas, etiquetas,\ncampos personalizados, plantillas de correo electrónico y campañas, y enviar tanto correos electrónicos\nde marketing en masa como correos transaccionales individuales, así como SMS.\n\n## Autenticación\n\nTodos los endpoints (excepto `POST /token` y los endpoints de OAuth) aceptan **tres\nmétodos de autenticación**. Utilice el que mejor se adapte a su stack.\n\n- **OAuth 2.0** (authorization_code + PKCE, o client_credentials): la\n  opción moderna para aplicaciones de terceros y plataformas de integración\n  (Zapier, Make, n8n) que actúan en nombre de un usuario de Mailpro. Emite tokens\n  de acceso revocables y delimitados por scopes. **El único método que ofrece\n  scopes granulares y que puede entregar con seguridad a un tercero.**\n- **Bearer JWT** (heredado): un token HMAC-HS256 obtenido mediante `POST /token`.\n  Tiene aspecto de OAuth, pero está vinculado a una única credencial de cuenta y no admite scopes.\n- **IdClient + ApiKey** (heredado): su ID de cliente + clave de API en las cabeceras HTTP.\n\n> Los métodos heredados Bearer JWT e IdClient+ApiKey **no aplican scopes**: conservan\n> acceso completo a todos los endpoints a los que ya podían acceder. Únicamente los\n> tokens de acceso OAuth 2.0 están sujetos a las comprobaciones de scope por endpoint\n> indicadas más abajo en cada operación (un token sin el scope adecuado recibe\n> **403 `insufficient_scope`**).\n\n### Método 1 — OAuth 2.0 (recomendado para integraciones)\n\nMailpro opera **un único** servidor de autorización OAuth 2.0 para todas sus APIs,\nexpuesto en una superficie única e independiente de la versión: **`https://api.mailpro.com/oauth/`**.\nUn servidor, un token: el **mismo** token de acceso funciona en CRM v3,\nEmail v2 y SMS. Los endpoints a los que un token puede llamar se rigen por\nlos **scopes** del token, no por la URL base de la API que invoque. No existe ningún\nendpoint OAuth por versión (no hay `/v3/oauth`, `/v2/oauth` ni `/sms/oauth`).\n\n| Endpoint | URL |\n|----------|-----|\n| Authorize | `GET https://api.mailpro.com/oauth/authorize` |\n| Token / Refresh | `POST https://api.mailpro.com/oauth/token` |\n| JWKS (verificación de firmas) | `GET https://api.mailpro.com/oauth/.well-known/jwks` |\n| Discovery | `GET https://api.mailpro.com/oauth/.well-known/openid-configuration` |\n\nEmisor (el claim `iss` de cada token): `https://api.mailpro.com/oauth/`.\n\n**Grants admitidos**\n\n- `authorization_code` **+ PKCE** (`code_challenge_method=S256` es\n  **obligatorio**): el flujo de consentimiento «Connect Mailpro» en el navegador.\n  Devuelve un token de acceso **y** un refresh token rotativo.\n- `client_credentials`: máquina a máquina, sin usuario. Devuelve únicamente un\n  token de acceso.\n- `refresh_token`: intercambia un refresh token por un nuevo par\n  acceso/refresh. **Los refresh tokens rotan**: cada uso invalida el anterior.\n\n**Vida útil de los tokens**\n\n| Token | TTL |\n|-------|-----|\n| Token de acceso (JWT RS256) | **20 minutos** |\n| Refresh token (deslizante, un solo uso) | **30 días** |\n| Código de autorización | **60 segundos** |\n\n**Authorization Code + PKCE: paso a paso**\n\n1. Genere `code_verifier` (43 a 128 caracteres aleatorios) y\n   `code_challenge = base64url(sha256(code_verifier))`.\n2. Redirija al usuario a:\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. El usuario aprueba en la pantalla de consentimiento de Mailpro; Mailpro le redirige de vuelta\n   con `?code=…&state=…`. Verifique `state` (protección CSRF).\n4. Intercambie el 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. Llame a cualquier API de Mailpro con `Authorization: Bearer ACCESS_TOKEN`.\n6. Cuando caduque, renueve con `grant_type=refresh_token` y almacene el\n   **nuevo** refresh token (el anterior queda inutilizado).\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\nEl mismo token de acceso OAuth también es aceptado por las APIs Email v2\n(`api.mailpro.com/v2`) y SMS (`api.mailpro.com/sms`): autentíquese\nuna vez, llame a cualquier API. Catálogo completo de scopes: véase **Scopes OAuth 2.0** más abajo.\n\n### Pruebe OAuth 2.0 en pocos minutos — inicio rápido\n\nLa forma más rápida de probar OAuth es el grant **client_credentials**\n(servidor a servidor, sin navegador, sin usuario). Necesita un par `client_id` /\n`client_secret` (solicítelo a su contacto de Mailpro o créelo en **Ajustes →\nAcceso a la API**). Sustituya los marcadores de posición de más abajo: **nunca\nincluya un secreto real en su repositorio**.\n\n**1. Obtenga un token de acceso**\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. Llame a un endpoint de recurso con el token**\n\n```bash\ncurl https://api.mailpro.com/v3/List \\\n  -H \"Authorization: Bearer eyJhbGciOiJSUzI1Ni…\"\n```\n\nEl **mismo** token también funciona en Email v2 (`…/v2/…`) y SMS (`…/sms/…`),\nsiempre que incluya el scope que cada endpoint requiera. Un token sin el\nscope necesario será rechazado con **403 `insufficient_scope`**.\n\n**«Connect Mailpro» interactivo (authorization_code + PKCE)**: cuando actúe\nen nombre de un *usuario* (Zapier/Make/n8n, aplicaciones partner), redirija al usuario a\n`GET https://api.mailpro.com/oauth/authorize` con `response_type=code`,\n`client_id`, `redirect_uri`, `scope`, `state`, `code_challenge` y\n`code_challenge_method=S256`; el usuario otorga su consentimiento; usted intercambia el\n`code` devuelto en `POST https://api.mailpro.com/oauth/token` con el\n`code_verifier` correspondiente. Recorrido completo más arriba; scopes en **Scopes OAuth 2.0** más abajo.\n\n> Los tokens son **JWT RS256**: los tokens de acceso tienen una vida de **20 minutos**\n> y los refresh tokens, **30 días** (deslizante, de un solo uso: cada renovación rota el token).\n> Verifique las firmas frente al JWKS en\n> `https://api.mailpro.com/oauth/.well-known/jwks` y valide el scope de\n> cada petición contra la matriz de scopes que figura más abajo.\n\n### Método 2 — Bearer JWT (heredado)\n\n```\nAuthorization: Bearer <access_token>\n```\n\nObtenga un token mediante `POST /token` (véase más abajo). Válido por defecto durante **30 días**;\nutilice `grant_type=refresh_token` para renovarlo sin volver a autenticarse. Es más sencillo\nde configurar, pero está vinculado a una única credencial de cuenta y **no** admite scopes ni\npuede entregarse a un tercero: para cualquier integración instalable, opte por OAuth 2.0.\n\n### Método 3 — IdClient + ApiKey (más sencillo)\n\nEnvíe su ID de cliente y su clave de API como **cabeceras HTTP** (recomendado) o como\n**parámetros de consulta**:\n\n```\nIdClient: 12345\nApiKey:   a1b2c3d4-e5f6-7890-abcd-ef1234567890\n```\n\nLas mismas credenciales que las APIs v2 / SMS: cópielas y péguelas desde\n**Ajustes → Acceso a la API**. Sin paso de intercambio de tokens. Se recomienda la\nforma con cabeceras, ya que las URL quedan registradas en IIS, Cloudflare y proxies.\n\n### Endpoint de token (grant 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`: su nombre de usuario de Mailpro (correo electrónico)\n- `password`: su clave de API (GUID), disponible en **Ajustes → Acceso a la API**\n\n### Endpoint de token (grant 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> El token emitido por `POST /v3/token` también es válido en la API Email v2\n> (`api.mailpro.com/v2`) y en la API SMS (`api.mailpro.com/sms`). Autentíquese\n> una sola vez y llame a cualquier API.\n\n## Scopes OAuth 2.0\n\nCuando su aplicación solicita un token de acceso OAuth, declara un conjunto de **scopes**:\npermisos que definen con precisión lo que el token puede hacer. El usuario ve una\ndescripción en lenguaje claro de cada scope solicitado en la pantalla de consentimiento.\nLos scopes se separan por espacios en el parámetro `scope`\n(`scope=contacts:read email:send`). Solicite únicamente lo que necesite.\n\nLos scopes siguen el patrón `<recurso>:<acción>` y son **jerárquicamente independientes**:\n`contacts:write` **no** implica `contacts:read`; solicite ambos si los necesita.\n\n| Scope | Permite |\n|-------|---------|\n| `contacts:read` | Consultar sus contactos, listas, etiquetas, segmentos y campos personalizados. |\n| `contacts:write` | Crear, editar y eliminar sus contactos, listas, etiquetas y campos personalizados. |\n| `email:send` | Enviar correos electrónicos transaccionales y de campaña en su nombre. |\n| `email:read` | Consultar estadísticas de entrega, aperturas, clics y el historial de rebotes. |\n| `email:templates:read` | Consultar sus plantillas de correo electrónico (asunto, cuerpo HTML y texto plano). |\n| `email:templates:write` | Crear, editar y eliminar sus plantillas de correo electrónico. |\n| `sms:send` | Enviar mensajes SMS transaccionales y de campaña en su nombre. |\n| `sms:read` | Consultar el saldo de créditos SMS y el historial de mensajes. |\n| `sms:contacts:read` | Consultar su agenda telefónica de SMS. |\n| `sms:contacts:write` | Crear, editar y eliminar entradas en su agenda telefónica de SMS. |\n| `webhooks:manage` | Suscribirse, editar y eliminar endpoints de webhook de su cuenta. |\n| `account:read` | Consultar los créditos, los límites y el estado de la suscripción de su cuenta. |\n\n### Aplicación de scopes por endpoint\n\nLas comprobaciones de scope son **activas** y se aplican por endpoint para los tokens\nemitidos por OAuth (la autenticación heredada queda exenta). Cada operación de esta referencia\nindica el scope requerido bajo el apartado **Authorization**. Resumen para la superficie CRM v3:\n\n| Endpoints | Scope requerido |\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`, comprobación de pertenencia a segmento | `contacts:read` |\n| `POST /Contact`, `POST /Contact/Multiple`, `PUT`/`DELETE /Contact/{id}`, añadir/quitar etiqueta o lista en un contacto, `POST`/`PUT`/`DELETE /List`, `POST`/`PUT`/`DELETE /Tag` | `contacts:write` |\n\n> Los endpoints de esta especificación que **no** aparecen más arriba (por ejemplo, `GET /Field`,\n> campañas, envío transaccional, envío de SMS, plantillas de mensajes, información de cuenta)\n> son hoy accesibles con cualquier token válido: aún no se aplican controles de scope sobre ellos\n> en la superficie CRM v3. Las APIs Email v2 y SMS aplican adicionalmente controles propios\n> a sus endpoints de envío / plantillas / contactos / cuenta (véanse esas especificaciones).\n\nUn rechazo por scope devuelve **403** con\n`{ \"error\": \"insufficient_scope\", \"required_scope\": \"…\" }` (RFC 6750 §3.1).\n\n## Formato de los datos\n\n- Todos los cuerpos de petición/respuesta utilizan **JSON**\n- Los nombres de propiedades están en **snake_case**\n- Los identificadores de contacto son **UUID**\n- Los identificadores de lista, etiqueta y campo son **enteros (int64)**\n\n## Paginación\n\n**Misma regla en las tres API de Mailpro** (Email v2, CRM v3, SMS): **5000 elementos por página,\npor defecto y como máximo.**\n- `numberOfRecords` — elementos por página. Ausente, `0` o por encima de 5000 = 5000.\n- `pageNumber` — número de página a partir de 1 (por defecto 1).\n- Ambos se aceptan como parámetro de query **o** como cabecera HTTP.\n- Una página llena significa que puede haber más: pida la página siguiente hasta recibir\n  una página incompleta.\n- Excepción: los mensajes y el historial con su HTML (`includeBodyHTML=true`) devuelven 10 elementos\n  por página por defecto.\n- Una página vacía, o una búsqueda sin resultados, devuelve `200` con una lista vacía `[]`.\n  `404` significa que el propio recurso (lista, etiqueta, segmento, campaña…) no existe.\n\n**Parámetros desconocidos**: se ignoran, nunca se rechazan. Cuando una solicitud lleva un parámetro que la API\nno reconoce, la respuesta incluye `X-Ignored-Parameters` (los nombres) y `X-Documentation` (un enlace a\nesta página), para que un error tipográfico no pase desapercibido.\n\n## Códigos de error de negocio\n\nLas infracciones de reglas de negocio devuelven HTTP **406** con un cuerpo JSON:\n```json\n{ \"Message\": \"Contact cannot be created. Email cannot be empty.\" }\n```\n\nCausas habituales de 406:\n- `Email cannot be empty`: el correo electrónico es obligatorio\n- `Email has an invalid format`: no es una dirección de correo electrónico válida\n- `A contact already exist with the same Email`: violación de unicidad de correo electrónico\n- `The email address was unsubscribed permanently`: dirección en lista negra\n- `The email address has identified has a hard bounce`: la dirección ha rebotado\n- `The email address has identified has a spam trap`: spam trap detectado\n- `At least one List is necessary`: los contactos deben pertenecer al menos a una lista\n- `One or more lists have reached the contact limit`: máximo de 1 M de contactos por lista\n- `Invalid Phone Number`: error de formato del número de teléfono\n- `Invalid country code for Phone Number`: prefijo de país no admitido\n- `Length exceeded for Phone Number (12)`: número de teléfono demasiado largo\n\n### Códigos de estado devueltos\n\n| Código | Significado |\n|--------|-------------|\n| `400` | Solicitud mal formada (JSON no válido, mensaje que supera el límite de partes SMS) |\n| `401` | Credenciales ausentes, no válidas o caducadas |\n| `402` | Créditos insuficientes para realizar el envío |\n| `403` | Permitido por sus credenciales, pero rechazado — cuenta bloqueada para el envío de SMS |\n| `404` | El objeto referenciado no existe (contacto, lista, etiqueta, campo, campaña) |\n| `406` | Violación de una regla de negocio de contacto (ver la lista anterior) |\n| `422` | Entrada rechazada por la validación, o límite de cuenta alcanzado |\n\n> **Cambio:** algunos de estos casos devolvían antes `404`. Los créditos\n> insuficientes ahora devuelven **402**, los errores de validación y los límites\n> de cuenta devuelven **422**, y una cuenta bloqueada para SMS devuelve **403**.\n> `404` ahora solo se usa para «este objeto no existe», de forma coherente con\n> la API SMS. Si su integración se basa en `404` para esos casos, actualícela\n> con los códigos anteriores.\n\n## Formato del número de teléfono\n\nLos números de teléfono se normalizan automáticamente. Puede enviarlos en numerosos formatos:\nlos espacios, guiones, puntos, barras, paréntesis, guiones bajos y espacios no separables\nse eliminan antes de la validación. Cualquier otro carácter (letras, apóstrofos,\n`=`, `;`, etc.) invalida el número.\n\n| Válido                                          | No válido                                          |\n|------------------------------------------------|----------------------------------------------------|\n| `+41794849980` — E.164 internacional           | `0612345678` — formato nacional (con `0` inicial)  |\n| `0041 79 484 99 80` — prefijo 00 + espacios    | `41'794'849'80` — apóstrofos (no se eliminan)      |\n| `+33 6-12-34-56-78` — + con guiones            | `tel=0041794849980` — caracteres adicionales       |\n| `41794849980` — dígitos sin prefijo            | `1234567` — demasiado corto (< 8 dígitos)          |\n\n**Reglas:**\n- Prefijos aceptados: `+`, `00` o sin prefijo\n- Longitud tras la normalización: **de 8 a 15 dígitos** (máximo E.164)\n- El primer dígito tras quitar el prefijo debe estar entre **1 y 9** (no `0`)\n- Separadores eliminados: espacio, tabulador, guion `-`, punto `.`, barra `/`, paréntesis `( )`, guion bajo `_`, NBSP\n- Cualquier otro carácter (letras, apóstrofos, `=`, `;`, `+` dentro del número, etc.) → rechazado\n- Forma almacenada en nuestra base de datos: dígitos sin prefijo (por ejemplo, `41794849980`)\n\n**Particularidades del Contact v3:** el campo `phone_number` de un Contact está actualmente\nlimitado a **12 dígitos** tras la normalización (frente a 15 en los endpoints SMS\nindependientes). Los números de países como Alemania (13), Brasil (13) o móviles de Italia\n(12-13) pueden ser rechazados en `/v3/Contact` con\n`Length exceeded for Phone Number (12)`. Esta restricción heredada podrá\nrelajarse en una próxima versión.\n\n## Límites\n\n| Recurso | Límite |\n|---------|--------|\n| Etiquetas por cuenta | 1000 |\n| Listas por cuenta | 1000 |\n| Campos personalizados por cuenta | 50 |\n| Peticiones API mensuales por cuenta | 50 000 (5× los créditos de correo electrónico de la suscripción) |\n\n## Seguimiento de entrega (emails transaccionales)\n\nCada email enviado **de forma unitaria** (`POST /Send/Mail`, la API Email v2, el relé SMTP o una automatización) recibe un identificador. Puede seguirse hasta que el servidor del destinatario lo acepte o lo rechace. Las campañas no están cubiertas aquí: utilice las estadísticas.\n\n1. **Envíe**: la respuesta de `POST /Send/Mail` contiene `id_single_send`.\n2. **Reciba el resultado** (recomendado): los eventos webhook `email.delivered` y `email.bounced` envían el estado final a su servidor en cuanto se conoce, sin ninguna consulta.\n3. **O consulte**: `GET /Send/{id}/Status` devuelve el estado actual (tabla más abajo).\n\n| `status` | Significado | ¿Final? |\n|---|---|---|\n| `queued` | Aceptado, en espera de envío. | no |\n| `scheduled` | Programado para más tarde (`date_planned`). | no |\n| `sent` | Entregado por nuestros servidores, aún sin respuesta del servidor del destinatario. | no |\n| `delivered` | Aceptado por el servidor del destinatario (`diag_code` = su respuesta SMTP). | **sí** |\n| `bounced` | Rechazado. `bounce_type`: `hard` = dirección no válida o rechazo definitivo; `soft` = rechazo tras nuevos intentos (buzón lleno, servidor inaccesible…). | **sí** |\n| `not_found` | Identificador desconocido o que no pertenece a su cuenta. | **sí** |\n\n**Consultar sin entrar en bucle**\n\n- Deje de consultar en cuanto el estado sea **final**: ya no cambiará.\n- Primera consulta, como pronto, **5 minutos** después del envío: `delivered` aparece unos 5 minutos después de la entrega.\n- Después, **una consulta cada 15 minutos como máximo**.\n- Si consulta un envío no final antes de 15 minutos, recibe la misma respuesta con la cabecera `Retry-After` (el número de segundos que debe esperar), y la llamada no cuenta en su cuota.\n- **Como máximo 48 horas** después del envío, cada email tiene un estado final: más allá, no consulte más.\n- `scheduled`: no es necesario consultar antes de `date_planned`.\n- **Cada consulta cuenta en su cuota de peticiones a la API.** Siguiendo estas reglas, un envío cuesta como máximo 192 peticiones (4 por hora durante 48 horas), y muchas menos en la práctica, ya que se detiene en el estado final. El mismo seguimiento por webhook cuesta un único evento.\n\nLos estados se conservan durante **365 días**.\n\n## Webhooks\n\nMailpro™ puede notificar a su servidor en tiempo real cuando se producen determinados eventos en su\ncuenta (se crea un contacto, se entrega un correo electrónico, finaliza una campaña, etc.).\nAl estilo de Stripe: su URL recibe un POST firmado con HMAC-SHA256 en cuanto el\nevento se desencadena.\n\nCada evento enviado a su webhook cuenta como 1 petición en su cuota de peticiones a la API.\n\n### Cómo funciona\n\n1. **Suscríbase** a uno o varios tipos de eventos mediante `POST /Webhook`. La respuesta\n   contiene un `secret` que se muestra **una sola vez** (al estilo de Stripe / GitHub): consérvelo.\n2. **Mailpro envía un POST** con un payload JSON firmado a su URL en cuanto un evento coincide.\n3. **Verifique la firma** en el lado del servidor con su `secret` (fragmentos de código más abajo).\n4. **Responda 200-299** en un plazo de 10 segundos. De lo contrario, reintentaremos con retroceso\n   exponencial (10 s → 1 min → 5 min → 30 min → 2 h → 6 h → 24 h, y después abandonamos).\n\n> **⏱ Retardo de activación**: tras crear, actualizar o eliminar un webhook, el\n> cambio tarda hasta **10 minutos** en propagarse por los servidores de la API de Mailpro.\n> Durante esa ventana puede que los eventos aún no coincidan con su nueva suscripción\n> (o que sigan coincidiendo con una que acaba de eliminarse). Es el tiempo necesario para\n> refrescar la caché en memoria de suscripciones en cada instancia de la API.\n\n### Envoltorio del payload\n\nTodos los eventos comparten un envoltorio común:\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 | Descripción |\n|-------|---------|-------------|\n| `id` | `evt_` + 32 hex | Identificador único del evento: utilícelo como clave de idempotencia por su parte. |\n| `type` | `<dominio>.<verbo>` | Tipo de evento canónico (por ejemplo, `email.delivered`, `contact.created`). |\n| `created` | Segundos Unix UTC | Incluido en la firma HMAC. |\n| `apiVersion` | `\"v3\"` | API de Mailpro que ha publicado el evento. |\n| `account.id` | int | Su ID de cliente de Mailpro. |\n| `account.name` | string | Nombre de su cuenta (útil para depurar en el lado cliente). |\n| `data` | object | Payload del evento: la forma depende de `type` (véase el catálogo a continuación). |\n\n### Catálogo de eventos\n\n| Evento | Disparador | Forma de `data` |\n|--------|------------|-----------------|\n| `ping` | Prueba mediante `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 añadido a una lista | `{ listId, listName, contactId, contactEmail, addedAt }` |\n| `list.contact_removed` | Contacto eliminado de una lista | `{ listId, listName, contactId, contactEmail, removedAt }` |\n| `tag.applied` | Etiqueta aplicada a un contacto | `{ tag, contactId, contactEmail, appliedAt }` |\n| `tag.removed` | Etiqueta retirada | `{ tag, contactId, contactEmail, removedAt }` |\n| `segment.matched` | Un contacto entra en un segmento | `{ segmentId, segmentName, contactId, contactEmail, matchedAt }` |\n| `email.processed` | Envío aceptado por el pipeline | `{ messageId, email, campaignId, processedAt }` |\n| `email.delivered` | Un correo **individual** fue aceptado por el servidor del destinatario — API v2/v3, SMTP o Automation (**no** campañas). Emitido desde el flujo de entrega de PowerMTA. | `{ messageId, email, idSending, sendingType, diagCode, deliveredAt }` |\n| `email.opened` | Pixel de seguimiento activado | `{ messageId, email, campaignId, openedAt, ip, userAgent }` |\n| `email.clicked` | Clic en un enlace con seguimiento | `{ messageId, email, campaignId, clickedAt, url, ip, userAgent }` |\n| `email.bounced` | Rebote hard o soft | `{ messageId, email, campaignId, bouncedAt, bounceType, reason }` |\n| `email.spam_report` | Reclamación FBL | `{ messageId, email, campaignId, reportedAt }` |\n| `email.unsubscribed` | Clic en el enlace de baja | `{ messageId, email, campaignId, unsubscribedAt }` |\n| `email.dropped` | Rechazado antes del envío | `{ messageId, email, campaignId, droppedAt, reason }` |\n| `campaign.started` | Comienza el envío | `{ campaignId, name, totalRecipients, startedAt }` |\n| `campaign.finished` | Todos los destinatarios procesados | `{ campaignId, name, totalSent, totalFailed, finishedAt }` |\n| `import.started` | Trabajo de importación iniciado | `{ importId, fileName, totalRows, startedAt }` |\n| `import.progress` | Al 25/50/75 % | `{ importId, fileName, processedRows, totalRows, progressPct }` |\n| `import.completed` | Importación correcta | `{ importId, fileName, totalRows, addedCount, updatedCount, errorCount, completedAt }` |\n| `import.failed` | Error fatal durante la importación | `{ importId, fileName, errorMessage, failedAt }` |\n\nSuscríbase a todos los eventos de esta API pasando `[\"*\"]` en el array `events`.\n\n### Firma HMAC\n\nCada POST incluye una cabecera `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. Extraiga `t` y `v1` de la cabecera.\n2. **Rechace** si `|now - t| > 300` (ventana de protección contra reenvío de 5 minutos).\n3. Calcule `expected = hex(HMAC_SHA256(secret, t + \".\" + raw_body))`.\n4. **Compare en tiempo constante** `v1` con `expected` (`crypto.timingSafeEqual`,\n   `hash_equals`, `CryptographicOperations.FixedTimeEquals`): nunca con `==`.\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### Reintentos y desactivación automática\n\n| Intento | Espera antes del siguiente intento |\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\nTras **10 fallos consecutivos** en el mismo webhook, este se **desactiva\nautomáticamente** (`isActive: false`) y usted recibe un correo electrónico de notificación. Vuelva\na activarlo con `PUT /Webhook/{id}` una vez que su endpoint esté reparado.\n\n### Éxito = HTTP 2xx\n\nConsideramos su webhook como correcto únicamente si devuelve un estado en `200-299`.\nCualquier 3xx/4xx/5xx o un timeout (> 10 s) se contabiliza como un fallo y desencadena reintentos.\nSi su endpoint está definitivamente fuera de servicio, devuelva `410 Gone` explícitamente: los reintentos\nseguirán produciéndose, pero el motivo del fallo quedará más claro en su registro de entregas.\n\n### Límites\n\n| Límite | Valor |\n|--------|-------|\n| Webhooks por cuenta | 20 |\n| Eventos suscritos por webhook | 50 |\n| Tamaño máximo del payload | 256 KB |\n| Timeout HTTP por intento | 10 segundos |\n| Retención del historial de entregas | 30 días |"
  },
  "servers": [
    {
      "url": "https://api.mailpro.com/v3",
      "description": "Producción"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "IdClientHeader": [],
      "ApiKeyHeader": []
    },
    {
      "IdClientQuery": [],
      "ApiKeyQuery": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Token JWT Bearer obtenido a través de `POST /token`.\nEnvíalo como: `Authorization: Bearer <token>`\n"
      },
      "OAuth2": {
        "type": "oauth2",
        "description": "Mailpro OAuth 2.0: la vía moderna para que aplicaciones de terceros y\nplataformas de integración (Zapier, Make, n8n) actúen en nombre de un\nusuario de Mailpro mediante tokens de acceso revocables y con ámbitos.\n\n- **authorizationCode + PKCE** (S256, obligatorio para clientes públicos):\n  el flujo de consentimiento «Conectar Mailpro». Emite un token de acceso\n  y un token de actualización rotativo.\n- **clientCredentials**: comunicación de máquina a máquina, sin usuario.\n\nUn único servidor de autorización para todas las API, en la superficie\nneutral en cuanto a versión `https://api.mailpro.com/oauth/` (emisor /\nclaim `iss` `https://api.mailpro.com/oauth/`). Los tokens de acceso son\nJWT RS256 (verifícalos contra el JWKS en\n`https://api.mailpro.com/oauth/.well-known/jwks`); TTL **20 minutos**.\nLos tokens de actualización son válidos durante **30 días** (deslizantes)\ny de **un solo uso** (se rotan en cada actualización). Los códigos de\nautorización tienen una vigencia de **60 segundos**.\n\nEl mismo token de acceso OAuth es aceptado por las tres API de Mailpro\n(CRM v3, Email v2 y SMS): autentícate una vez y llama a cualquier API;\nel acceso se rige por los ámbitos del token, no por la URL base de la\nAPI. La autenticación heredada (Bearer JWT / IdClient+ApiKey) sigue\nfuncionando sin cambios y NO está sujeta a control de á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": "Tu ID numérico de cliente de Mailpro: transporte preferido (cabecera HTTP).\nLas URL quedan registradas en los servidores web, en Cloudflare y en\nlos proxies, por lo que las credenciales pasadas por cadena de consulta\npueden filtrarse en los registros de acceso. Las cabeceras no se registran.\n"
      },
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "ApiKey",
        "description": "Tu clave de API (GUID): transporte preferido (cabecera HTTP)."
      },
      "IdClientQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "IdClient",
        "description": "Tu ID numérico de cliente de Mailpro: heredado, conservado por\ncompatibilidad con versiones anteriores. Migra a la cabecera `IdClient`\ncuando puedas.\n"
      },
      "ApiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "ApiKey",
        "description": "Tu clave de API (GUID): heredada, conservada por compatibilidad con versiones anteriores."
      }
    },
    "schemas": {
      "TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string",
            "description": "Token JWT de acceso: úsalo en todas las solicitudes posteriores como `Authorization: Bearer <token>`"
          },
          "token_type": {
            "type": "string",
            "example": "bearer"
          },
          "expires_in": {
            "type": "integer",
            "description": "Duración del token, en segundos",
            "example": 31535999
          },
          "userName": {
            "type": "string",
            "description": "ID de cliente del usuario autenticado",
            "example": "12345"
          },
          "refresh_token": {
            "type": "string",
            "description": "Token para obtener un nuevo token de acceso sin volver a autenticarse"
          },
          "application": {
            "type": "string",
            "description": "Nombre de la integración (solo para concesiones de terceros)",
            "nullable": true
          }
        }
      },
      "Contact": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identificador único del contacto (UUID)",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "email": {
            "type": "string",
            "description": "Dirección de correo electrónico del contacto",
            "example": "alice@example.com"
          },
          "phone_number": {
            "type": "string",
            "description": "Número de teléfono: acepta `+41…`, `0041…` o `41…` con o sin separadores. Consulta **Formato del número de teléfono** en la descripción de la API.",
            "example": "+41791234567"
          },
          "tags": {
            "type": "array",
            "description": "Etiquetas asignadas a este contacto (se incluyen cuando `includeTags=true`)",
            "items": {
              "$ref": "#/components/schemas/TagRef"
            }
          },
          "lists": {
            "type": "array",
            "description": "Listas a las que pertenece este contacto (se incluyen cuando `includeLists=true`)",
            "items": {
              "$ref": "#/components/schemas/ListRef"
            }
          },
          "custom_fields": {
            "type": "array",
            "description": "Valores de los campos personalizados de este contacto",
            "items": {
              "$ref": "#/components/schemas/FieldValue"
            }
          }
        }
      },
      "ContactEntry": {
        "type": "object",
        "required": [
          "email",
          "lists"
        ],
        "properties": {
          "email": {
            "type": "string",
            "description": "Dirección de correo electrónico del contacto (obligatoria, debe ser única)",
            "example": "alice@example.com"
          },
          "phone_number": {
            "type": "string",
            "description": "Número de teléfono: acepta `+41…`, `0041…` o `41…` con o sin separadores.\nDebe comenzar por un código de país válido. Máximo 12 dígitos en Contact\n(consulta **Formato del número de teléfono** en la descripción de la API\npara las reglas completas).\n",
            "example": "+41791234567"
          },
          "lists": {
            "type": "array",
            "description": "Listas a las que asignar este contacto. **Es obligatoria al menos una lista.**",
            "items": {
              "$ref": "#/components/schemas/ListRef"
            },
            "example": [
              {
                "id": 101
              }
            ]
          },
          "tags": {
            "type": "array",
            "description": "Etiquetas que asignar a este contacto (opcional)",
            "items": {
              "$ref": "#/components/schemas/TagRef"
            },
            "example": [
              {
                "id": 5
              }
            ]
          },
          "custom_fields": {
            "type": "array",
            "description": "Valores de los campos personalizados de 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 de la etiqueta (de `GET /Tag`)",
            "example": 5
          }
        }
      },
      "ListRef": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "ID de la lista (de `GET /List`)",
            "example": 101
          }
        }
      },
      "FieldValue": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "ID del campo personalizado (de `GET /Field`)",
            "example": 1
          },
          "value": {
            "type": "string",
            "description": "Valor que se almacenará para este campo. El tipo debe coincidir con el tipo del campo.",
            "example": "Alice"
          }
        }
      },
      "List": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Identificador único de la lista",
            "example": 101
          },
          "name": {
            "type": "string",
            "description": "Nombre visible de la lista",
            "example": "Newsletter subscribers"
          },
          "total_contacts": {
            "type": "integer",
            "description": "Número total de contactos en la lista (se incluye cuando `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": "Nombre visible de la lista",
            "example": "Newsletter subscribers — France"
          }
        }
      },
      "Tag": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Identificador único de la etiqueta",
            "example": 5
          },
          "name": {
            "type": "string",
            "description": "Nombre visible de la etiqueta",
            "example": "VIP customers"
          }
        }
      },
      "TagEntry": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Nombre visible de la etiqueta",
            "example": "VIP customers"
          }
        }
      },
      "Field": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Identificador único del campo personalizado",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Nombre interno del campo",
            "example": "first_name"
          },
          "personalisation_key": {
            "type": "string",
            "description": "Nombre de variable utilizado para la personalización en las plantillas de correo (p. ej. `{{first_name}}`)",
            "example": "first_name"
          },
          "field_type": {
            "type": "string",
            "description": "Tipo de dato del campo",
            "enum": [
              "text",
              "number",
              "date"
            ],
            "example": "text"
          }
        }
      },
      "FieldEntry": {
        "type": "object",
        "required": [
          "name",
          "id_field_type",
          "personalize_key"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Nombre visible del campo",
            "example": "First name"
          },
          "id_field_type": {
            "type": "integer",
            "description": "ID del tipo de campo (tipos disponibles mediante `GET /Field/Types`):\n- `1` = texto\n- `2` = número entero\n- `3` = fecha y hora\n- `4` = sí / no\n- `12` = número decimal\n",
            "example": 1
          },
          "personalize_key": {
            "type": "string",
            "description": "Clave de variable de la plantilla, para insertar el campo en un mensaje",
            "example": "first_name"
          },
          "sort": {
            "type": "integer",
            "minimum": 0,
            "maximum": 255,
            "description": "Orden de visualización (0 a 255). Obligatorio para modificar un campo (`PUT /Field/{id}`).",
            "example": 255
          }
        }
      },
      "CampaignEntry": {
        "type": "object",
        "properties": {
          "campaign_title": {
            "type": "string",
            "description": "Nombre visible de la campaña",
            "example": "Spring Newsletter April 2026"
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Hora de envío programada (ISO 8601). Omítela para enviar de inmediato.",
            "example": "2026-04-20T09:00:00"
          }
        }
      },
      "SendEmailRequest": {
        "type": "object",
        "description": "Solicitud de correo transaccional. Proporciona **o bien** `email` (dirección\nen línea) **o bien** `id_contact` (contacto del CRM), no ambos.\nProporciona **o bien** `body_html` (HTML en línea) **o bien** `id_message`\n(plantilla guardada), no ambos.\n",
        "properties": {
          "id_sender_email": {
            "type": "integer",
            "description": "ID de la dirección de correo del remitente (de `GET /SenderEmail`). Obligatorio.",
            "example": 201
          },
          "email": {
            "type": "string",
            "description": "Dirección de correo del destinatario (en línea). Indica esto **O** `id_contact`.\nSi se indican ambos, `id_contact` tiene prioridad.\n\n**La parte local** (antes de `@`) acepta letras, dígitos y `. _ - +` —\nincluido el sub-addressing RFC 5322 (`user+tag@domain`, p. ej. Gmail, Fastmail,\nProton). Los demás caracteres especiales que permite la RFC 5322\n(p. ej. `! # $ % & ' * / = ? ^ { | } ~`) y las partes locales entre comillas **no**\nse aceptan.\n",
            "example": "alice@example.com"
          },
          "id_contact": {
            "type": "string",
            "format": "uuid",
            "description": "ID del contacto del CRM destinatario. Proporciona este **O** `email`.\nUsar `id_contact` habilita la personalización con los valores de los\ncampos personalizados del contacto.\n",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "subject": {
            "type": "string",
            "description": "Asunto del correo electrónico (máx. 255 caracteres). Obligatorio.",
            "example": "Your order #12345 has been shipped"
          },
          "body_html": {
            "type": "string",
            "description": "Contenido HTML en línea. Proporciona este **O** `id_message`.\nSi se proporcionan ambos, `id_message` tiene prioridad.\n",
            "example": "<p>Hello {{first_name}}, your order has shipped!</p>"
          },
          "id_message": {
            "type": "integer",
            "description": "ID de una plantilla de mensaje guardada (de `GET /Message`).\nProporciona este **O** `body_html`.\n",
            "example": 501
          },
          "reply_to": {
            "type": "string",
            "description": "Dirección de respuesta (por defecto, la dirección del remitente)",
            "example": "support@yourcompany.com"
          },
          "fields": {
            "type": "array",
            "description": "Campos de personalización (solo se usan con `email` en línea, no con `id_contact`)",
            "items": {
              "$ref": "#/components/schemas/FieldValue"
            }
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Programa el envío para una fecha futura. Omítelo para enviar de inmediato.",
            "example": "2026-04-15T14:00:00"
          },
          "attachments": {
            "type": "array",
            "maxItems": 5,
            "description": "Archivos adjuntos opcionales (máx. **5**). Cada elemento es un objeto con un `filename` y un `content` codificado en Base64. Límites: **10 MB por archivo** y **15 MB en total**, medidos tras la decodificación Base64. Tipos aceptados: PDF, Word, Excel, PowerPoint, imágenes (JPG, PNG, GIF, WebP), ZIP, CSV, TXT — los archivos ejecutables y scripts se rechazan. **Facturación:** el envío mantiene su crédito habitual, más **1 crédito por cada 512 KB de adjuntos más allá de los primeros 512 KB** (los adjuntos ≤ 512 KB no añaden nada).",
            "items": {
              "$ref": "#/components/schemas/EmailAttachment"
            }
          },
          "keep_in_my_files": {
            "type": "boolean",
            "default": false,
            "description": "`false` (por defecto): los archivos adjuntos solo se usan para este envío — se almacenan fuera de\nsu cuenta, no cuentan en su cuota de almacenamiento y se eliminan automáticamente después de\n35 días. `true`: los archivos adjuntos también se guardan en los archivos de su cuenta (carpeta\n`Secure/Files/Sending/api`) y cuentan en su cuota de almacenamiento.\n",
            "example": false
          }
        }
      },
      "EmailAttachment": {
        "type": "object",
        "required": [
          "filename",
          "content"
        ],
        "description": "Un único archivo adjunto de correo con contenido codificado en Base64.",
        "properties": {
          "filename": {
            "type": "string",
            "description": "Nombre de archivo original mostrado al destinatario, p. ej. invoice.pdf.",
            "example": "invoice.pdf"
          },
          "content": {
            "type": "string",
            "format": "byte",
            "description": "Contenido del archivo, codificado en Base64.",
            "example": "JVBERi0xLjQKJVBERi0xLjQK..."
          }
        }
      },
      "SendCampaignRequest": {
        "type": "object",
        "required": [
          "id_message",
          "id_sender_email",
          "lists",
          "campaign_title"
        ],
        "properties": {
          "id_message": {
            "type": "integer",
            "description": "ID de la plantilla de mensaje que se va a usar (de `GET /Message`)",
            "example": 501
          },
          "id_sender_email": {
            "type": "integer",
            "description": "ID de la dirección del remitente (de `GET /SenderEmail`)",
            "example": 201
          },
          "id_email_reply_to": {
            "type": "integer",
            "description": "ID de la dirección del remitente para respuestas (obtenido con `GET /SenderEmail`). Debe ser una de las direcciones de remitente de su cuenta; de lo contrario, la solicitud se rechaza. Por defecto: `id_sender_email`.",
            "example": 201
          },
          "lists": {
            "type": "array",
            "description": "Listas de contactos destinatarias (se requiere al menos una)",
            "items": {
              "$ref": "#/components/schemas/ListRef"
            },
            "example": [
              {
                "id": 101
              }
            ]
          },
          "id_segment": {
            "type": "integer",
            "description": "Filtra los destinatarios mediante un segmento guardado (opcional)",
            "example": 7
          },
          "campaign_title": {
            "type": "string",
            "description": "Nombre visible de esta campaña (máx. 150 caracteres)",
            "example": "Spring Newsletter April 2026"
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Hora de envío programada (ISO 8601). Omítela para enviar de inmediato.",
            "example": "2026-04-20T09:00:00"
          },
          "id_automation": {
            "type": "integer",
            "description": "Vincula este envío a un flujo de trabajo de automatización (opcional)",
            "example": 3
          }
        }
      },
      "SmsSingleRequest": {
        "type": "object",
        "required": [
          "phone_number",
          "message"
        ],
        "properties": {
          "phone_number": {
            "type": "string",
            "description": "Número de teléfono del destinatario: acepta `+41…`, `0041…` o `41…` con o sin separadores. Consulta **Formato del número de teléfono** en la descripción de la API.",
            "example": "+41791234567"
          },
          "message": {
            "type": "string",
            "description": "Texto del SMS (máx. 160 caracteres con codificación estándar)",
            "example": "Your verification code is 9473. Valid for 10 minutes."
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Programa el envío. Omítelo para enviar de inmediato.",
            "example": "2026-04-15T14:00:00"
          }
        }
      },
      "SmsCampaignRequest": {
        "type": "object",
        "required": [
          "lists",
          "message"
        ],
        "properties": {
          "lists": {
            "type": "array",
            "description": "Listas de contactos destinatarias (utiliza el campo phone_number de cada contacto)",
            "items": {
              "$ref": "#/components/schemas/ListRef"
            },
            "example": [
              {
                "id": 101
              }
            ]
          },
          "message": {
            "type": "string",
            "description": "Texto del SMS",
            "example": "Flash sale today — 20% off all products. Shop at mailpro.com"
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Programa el envío. Omítelo para enviar de inmediato.",
            "example": "2026-04-20T10:00:00"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "Message": {
            "type": "string",
            "description": "Mensaje de error legible para humanos",
            "example": "Contact cannot be created. Email cannot be empty."
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "ID del webhook (Mailpro)",
            "example": 42
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Punto de conexión HTTPS que recibe los eventos",
            "example": "https://example.com/hooks/mailpro"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tipos de evento suscritos",
            "example": [
              "email.delivered",
              "email.bounced",
              "contact.created"
            ]
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Descripción libre (para tu propia referencia)",
            "example": "Slack integration"
          },
          "isActive": {
            "type": "boolean",
            "description": "Cuando es `false`, no se entrega ningún evento. Se desactiva automáticamente tras 10 fallos consecutivos.",
            "example": true
          },
          "consecutiveFailures": {
            "type": "integer",
            "description": "Número de intentos de entrega fallidos consecutivos (se restablece tras un éxito)",
            "example": 0
          },
          "autoDisabledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Se establece cuando `isActive` se cambió a `false` automáticamente"
          },
          "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": "Punto de conexión HTTPS (HTTP se rechaza en producción)",
            "example": "https://example.com/hooks/mailpro"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "maxItems": 50,
            "description": "Lista de tipos de evento a los que suscribirse (consulta el catálogo anterior). Usa `[\"*\"]` para suscribirte a todos los eventos de esta API.",
            "example": [
              "email.delivered",
              "email.bounced",
              "contact.created"
            ]
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "description": "Descripción libre para tu propia referencia",
            "example": "Slack integration"
          }
        }
      },
      "WebhookCreateResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Webhook"
          },
          {
            "type": "object",
            "properties": {
              "secret": {
                "type": "string",
                "description": "Secreto HMAC. **Se muestra una sola vez aquí, nunca más.** Guárdalo en el servidor: se utiliza para firmar todas las cargas útiles enviadas a tu URL.",
                "example": "whsec_AbcD1234EfgH5678IjkL9012MnoP3456QrsT"
              }
            }
          }
        ]
      },
      "WebhookUpdate": {
        "type": "object",
        "description": "Actualización parcial: todos los campos son opcionales.",
        "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": "Establece `true` para volver a habilitar un webhook desactivado automáticamente una vez corregido tu punto de conexión."
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "ID del intento de entrega"
          },
          "eventId": {
            "type": "string",
            "description": "UUID del evento (= el campo `id` en la carga útil que recibió tu punto de conexión)",
            "example": "evt_8f3a9b2c4d1e5f6789abcdef01234567"
          },
          "eventType": {
            "type": "string",
            "example": "email.delivered"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "success",
              "retrying",
              "failed"
            ],
            "description": "Estado actual de esta entrega"
          },
          "statusCode": {
            "type": "integer",
            "nullable": true,
            "description": "Código de estado HTTP recibido de tu punto de conexión (`null` si aún no se ha intentado o en caso de tiempo de espera agotado)",
            "example": 200
          },
          "responseTimeMs": {
            "type": "integer",
            "nullable": true,
            "example": 142
          },
          "attemptCount": {
            "type": "integer",
            "description": "Número de intentos hasta el momento (1–7, después la entrega falla de forma permanente)"
          },
          "lastError": {
            "type": "string",
            "nullable": true,
            "description": "Primeros 2 KB del cuerpo de la respuesta o del mensaje de excepción cuando un intento falla"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "nextAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Momento en que se realizará el próximo reintento: `null` si la entrega tuvo éxito o se abandonó de forma permanente."
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Authentication",
      "description": "Obtén un token JWT Bearer para autenticar todas las demás llamadas a la API.\nEl endpoint de token admite varios tipos de concesión: `password`, `refresh_token`,\n`client_credentials` y las integraciones de terceros (`zapier`, `automateio`, `microsoft-flow`).\n"
    },
    {
      "name": "Account",
      "description": "Recupera el perfil de la cuenta, el saldo de créditos y los detalles de la suscripción."
    },
    {
      "name": "Contact",
      "description": "CRUD completo de los contactos del CRM. Los contactos almacenan una dirección de\ncorreo electrónico, un número de teléfono opcional, valores de campos personalizados\ny pertenecen a una o varias **Listas**. También pueden etiquetarse con **Etiquetas**.\nLos ID de contacto son UUID.\n"
    },
    {
      "name": "List",
      "description": "Gestiona las listas de contactos. Las listas agrupan contactos para campañas y segmentación.\nMáximo 1000 listas por cuenta (mismo límite en las tres API de Mailpro y en la app). Máximo 1 000 000 de contactos por lista.\n"
    },
    {
      "name": "Tag",
      "description": "Gestiona etiquetas para clasificar y segmentar contactos.\nLas etiquetas son rótulos planos (p. ej. «VIP», «Inactivos», «B2B»).\nMáximo 1000 etiquetas por cuenta.\n"
    },
    {
      "name": "Field",
      "description": "Gestiona campos personalizados de contacto. Los campos te permiten almacenar datos\nadicionales en cada contacto (p. ej. nombre, ciudad, nivel de fidelización).\nMáximo 50 campos por cuenta.\nTipos de campo: `text`, `number`, `date`.\n"
    },
    {
      "name": "Message",
      "description": "Gestiona plantillas de correo electrónico en HTML. Las plantillas almacenan el asunto,\nel cuerpo HTML y la configuración del enlace de cancelación de suscripción.\nReutiliza las plantillas en varias campañas.\n"
    },
    {
      "name": "Campaign",
      "description": "Consulta las estadísticas de campaña y gestiona las campañas programadas.\nLas campañas pueden editarse o eliminarse hasta 5 minutos antes de la hora de envío programada.\n"
    },
    {
      "name": "Send",
      "description": "Envía campañas masivas de correo electrónico (a listas de contactos) y correos transaccionales individuales.\nLas campañas pueden programarse o enviarse de inmediato.\n"
    },
    {
      "name": "Sender Addresses",
      "description": "Gestiona las direcciones de remitente validadas. Solo las direcciones verificadas pueden utilizarse como `From`.\n"
    },
    {
      "name": "History",
      "description": "Consulta el registro de envíos de correo electrónico y SMS pasados."
    },
    {
      "name": "Segment",
      "description": "Gestiona segmentos dinámicos: reglas de filtrado guardadas que seleccionan\nautomáticamente contactos en función de valores de campos, comportamiento\no pertenencia a listas.\n"
    },
    {
      "name": "SMS",
      "description": "Envía mensajes SMS a contactos individuales o a listas de contactos completas.\nUtiliza el campo `phone_number` de cada contacto.\n"
    },
    {
      "name": "Folders",
      "description": "Organiza las plantillas de mensajes en carpetas."
    },
    {
      "name": "Import",
      "description": "Sube un archivo CSV o XLSX para importar contactos a una lista de forma masiva.\nEl archivo se pone en cola para su procesamiento asíncrono: realiza el seguimiento\ndel proceso mediante el `import_job_id` devuelto o el callback opcional `webhook_url`.\n"
    },
    {
      "name": "Webhooks",
      "description": "Suscribe tu servidor a notificaciones de eventos en tiempo real. Mailpro publica\nuna carga útil JSON firmada con HMAC en tu URL en cuanto se produce el evento suscrito.\nConsulta la sección «Webhooks» en la introducción para ver el catálogo completo\nde eventos, el sobre de la carga útil, fragmentos de verificación de firma y la\npolítica de reintentos.\n"
    },
    {
      "name": "Suppression",
      "description": "Rebotes y bajas de la cuenta: listar, añadir, eliminar."
    }
  ],
  "paths": {
    "/token": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "getAccessToken",
        "summary": "Obtener un token de acceso",
        "security": [],
        "description": "Obtiene un token JWT Bearer mediante credenciales de cliente.\nEnvía el `access_token` devuelto como `Authorization: Bearer <token>` en todas las solicitudes posteriores.\n\n### Tipos de concesión admitidos\n\n| Tipo de concesión | Caso de uso |\n|------------|----------|\n| `password` | Integración estándar: nombre de usuario + clave de API |\n| `refresh_token` | Renueva un token de acceso a punto de caducar sin volver a autenticarse |\n| `client_credentials` | De servidor a servidor solo con el ID de cliente |\n| `zapier` / `automateio` / `microsoft-flow` | Integraciones de plataformas de terceros |\n\n### Duración del token\n- Token de acceso: **30 días**\n- Token de actualización: **90 días**\n\nUsa `grant_type=refresh_token` para obtener un token de acceso nuevo sin\nvolver a autenticarte: tu SDK debería hacerlo automáticamente cuando al\ntoken de acceso le quede poco tiempo de validez (estrategia típica:\nactualizar cuando queden menos de 24 h).\n\n### Almacenamiento seguro de tokens\nIncluso con una vigencia de 30 días, cualquier persona que obtenga un\n`access_token` puede suplantar a tu cuenta hasta que caduque. Almacena\nlos tokens en un almacén de secretos del servidor (Vault, AWS Secrets\nManager, Azure Key Vault, variables de entorno en un host bastionado).\n**Nunca** incrustes un token en:\n- JavaScript del lado del cliente ni en cualquier paquete del navegador\n- aplicaciones móviles distribuidas a los usuarios finales (el binario puede inspeccionarse)\n- repositorios Git públicos ni registros de compilación de CI\nSi se filtra un token, revócalo desde la configuración de tu cuenta de\nMailpro y solicita uno nuevo.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "description": "Tipo de concesión de autenticación",
                    "enum": [
                      "password",
                      "refresh_token",
                      "client_credentials",
                      "zapier",
                      "automateio",
                      "microsoft-flow"
                    ],
                    "example": "password"
                  },
                  "username": {
                    "type": "string",
                    "description": "Tu nombre de usuario de Mailpro (correo electrónico). Obligatorio para la concesión `password`.",
                    "example": "user@yourcompany.com"
                  },
                  "password": {
                    "type": "string",
                    "description": "Tu clave de API de Mailpro (formato GUID). Obligatoria para la concesión `password`.",
                    "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  },
                  "refresh_token": {
                    "type": "string",
                    "description": "Token de actualización anterior. Obligatorio para la concesión `refresh_token`."
                  },
                  "client_id": {
                    "type": "string",
                    "description": "Tu ID de cliente de Mailpro (entero). Obligatorio para la concesión `client_credentials`.",
                    "example": "12345"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token emitido correctamente",
            "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": "Credenciales no válidas o tipo de concesión no admitido",
            "content": {
              "application/json": {
                "examples": {
                  "invalid_grant": {
                    "summary": "Credenciales incorrectas",
                    "value": {
                      "error": "invalid_grant",
                      "error_description": "The username or password is incorrect."
                    }
                  },
                  "no_api_access": {
                    "summary": "Sin acceso a la API",
                    "value": {
                      "error": "no_api_access",
                      "error_description": "The account doesn't have API access."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/Account/Me": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getCrmAccountMe",
        "summary": "Obtener detalles de la cuenta",
        "description": "Devuelve los detalles del perfil y la configuración de la cuenta de Mailpro autenticada.",
        "responses": {
          "200": {
            "description": "Detalles de la cuenta devueltos correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Account/Credits": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getCrmAccountCredits",
        "summary": "Obtener el saldo de créditos",
        "description": "Devuelve el saldo de créditos restantes de correo electrónico y SMS de la cuenta.",
        "responses": {
          "200": {
            "description": "Saldo de créditos devuelto correctamente",
            "content": {
              "application/json": {
                "example": {
                  "email_credits": 48500,
                  "sms_credits": 1200
                }
              }
            }
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Account/SubscriptionEndDate": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getCrmSubscriptionEndDate",
        "summary": "Obtener la fecha de fin de la suscripción",
        "description": "Devuelve la fecha de caducidad de la suscripción actual de Mailpro.",
        "responses": {
          "200": {
            "description": "Fecha de fin de la suscripción devuelta correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Account/Limits": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getCrmAccountLimits",
        "summary": "Obtener la cuota mensual de la API",
        "description": "Devuelve la cuota mensual de solicitudes de la API para el cliente autenticado.\n\n**Cálculo de la cuota:** el límite se calibra a partir de los créditos\nde la suscripción de correo electrónico (normalmente,\n`créditos_de_suscripción × 5`). Para un plan de 10 000 correos/mes,\nesto genera un presupuesto mensual de 50 000 solicitudes. La\ncorrespondencia exacta la establece el proceso de facturación en el\nalta de la suscripción y se restablece en la **fecha de aniversario\nde la suscripción** (no el día 1 del mes natural).\n\n**Qué consume créditos:**\n- Cada solicitud autenticada a la API en los endpoints de `v2`, `v3`\n  o `sms` (cada llamada = 1 crédito, sea cual sea el resultado: tanto\n  los 2xx como la mayoría de los errores de negocio 4xx cuentan).\n- Cada evento de webhook enviado a un endpoint suscrito (p. ej.\n  `contact.created` enviado a tu webhook suscrito = 1 crédito\n  además de la llamada a la API que lo desencadenó).\n\n**Qué es GRATUITO (no consume créditos):**\n- Los endpoints `/health` (monitorización).\n- Las solicitudes que fallan con `401` (fallo de autenticación),\n  `403` (prohibido) o `429` (limitadas por tasa).\n- Los eventos de webhook para clientes sin suscripción coincidente\n  (cortocircuito en caché: sin impacto en la cuota).\n",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "LegacyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Instantánea de la cuota",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "monthly_limit": {
                      "type": "integer",
                      "description": "Total de créditos asignados para el mes de facturación en curso.",
                      "example": 50000
                    },
                    "remaining": {
                      "type": "integer",
                      "description": "Créditos aún disponibles antes del próximo restablecimiento.",
                      "example": 32847
                    },
                    "used": {
                      "type": "integer",
                      "description": "Créditos consumidos desde el último restablecimiento (max(0, monthly_limit - remaining)).",
                      "example": 17153
                    },
                    "is_free": {
                      "type": "boolean",
                      "description": "True en cuentas de nivel gratuito.",
                      "example": false
                    },
                    "remained_sends_for_free": {
                      "type": "integer",
                      "nullable": true,
                      "description": "Contador específico del nivel gratuito. `null` en cuentas de pago.",
                      "example": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "No se ha encontrado ningún registro de cuota para esta cuenta (cuenta recién creada que nunca ha realizado una llamada a la API; la fila se creará en la primera solicitud autenticada)."
          }
        }
      }
    },
    "/Contact": {
      "post": {
        "tags": [
          "Contact"
        ],
        "operationId": "createContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Crear un contacto",
        "description": "Crea un nuevo contacto del CRM con campos personalizados, asignaciones de listas y etiquetas opcionales.\n\n**Obligatorio:**\n- `email`: debe ser único en la cuenta\n- `lists`: al menos una asignación de lista es obligatoria\n\n**Los números de teléfono** aceptan `+41…`, `0041…` o `41…` con o sin\nseparadores (máx. 12 dígitos en Contact; consulta **Formato del número\nde teléfono** en la descripción de la API).\n\n**Los campos personalizados** se identifican por su `id` (de `GET /Field`).\nLos valores se validan según el tipo del 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 creado correctamente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "400": {
            "description": "Formato de correo electrónico no válido"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "406": {
            "description": "Infracción de regla de negocio (consulta la descripción para la lista completa)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "email_exists": {
                    "summary": "Correo electrónico duplicado",
                    "value": {
                      "Message": "Contact cannot be created. A contact already exist with the same Email"
                    }
                  },
                  "no_list": {
                    "summary": "No se ha proporcionado ninguna lista",
                    "value": {
                      "Message": "Contact cannot be created. At least one List is necessary"
                    }
                  },
                  "unsubscribed_forever": {
                    "summary": "Suscripción cancelada de forma 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 (en masa)",
        "description": "Crea hasta **1000 contactos** en una sola solicitud para una importación masiva eficiente.\nLa respuesta incluye una lista de los contactos creados correctamente y una lista\nde errores correspondientes a los contactos que no se pudieron crear (p. ej. correo\nelectrónico duplicado, datos no válidos).\n\n**Nota:** las listas y etiquetas utilizadas en importaciones masivas **no** deben\nestar vinculadas a flujos de trabajo de automatización activos. Si lo están, toda\nla importación se rechaza con 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": "Importación masiva completada (consulta el array `errors` para ver los fallos parciales)",
            "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 no válida"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "406": {
            "description": "Infracción de regla de negocio (p. ej. conflicto de automatización, límite de listas alcanzado)"
          }
        }
      }
    },
    "/Contact/{id}": {
      "get": {
        "tags": [
          "Contact"
        ],
        "operationId": "getContact",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Obtener un contacto",
        "description": "Devuelve los detalles de un contacto específico.\nUsa `includeTags=true` para devolver también las etiquetas asignadas al contacto.\nUsa `includeLists=true` para devolver también las listas a las que pertenece el contacto.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID del contacto (de `POST /Contact` o `GET /List/{id}/Contact`)",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "includeTags",
            "in": "query",
            "description": "Establece en `true` para incluir las asignaciones de etiquetas del contacto en la respuesta",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "includeLists",
            "in": "query",
            "description": "Establece en `true` para incluir las pertenencias a listas del contacto en la respuesta",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contacto devuelto correctamente",
            "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 no válido o caducado"
          },
          "404": {
            "description": "Contacto no encontrado"
          }
        }
      },
      "put": {
        "tags": [
          "Contact"
        ],
        "operationId": "updateContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Actualizar un contacto",
        "description": "Actualiza el correo electrónico, el número de teléfono, los campos personalizados,\nlas asignaciones de listas y las etiquetas de un contacto.\nEl cuerpo de la solicitud reemplaza los datos actuales del contacto: incluye todas las\nlistas y etiquetas a las que quieras que el contacto siga estando asignado.\n\n**Obligatorio en el cuerpo:** `email` y al menos una entrada `lists`.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID del 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 actualizado correctamente",
            "content": {
              "application/json": {
                "example": {
                  "Result": "Successful operation"
                }
              }
            }
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Contacto no encontrado"
          },
          "422": {
            "description": "Error de validación (formato de correo, número de teléfono, tipo de valor de campo no coincidente)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Contact"
        ],
        "operationId": "deleteContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Eliminar un contacto",
        "description": "Elimina un contacto del CRM de forma permanente.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID del contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          }
        ],
        "responses": {
          "200": {
            "description": "Contacto eliminado correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Contacto no encontrado"
          }
        }
      }
    },
    "/Contact/{id}/Activity": {
      "get": {
        "tags": [
          "Contact"
        ],
        "operationId": "getCrmContactActivity",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Actividad de un contacto en 90 días",
        "description": "Correos recibidos, abiertos y con clics, cliente de correo preferido y últimas campañas del contacto.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "GUID del contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Actividad devuelta",
            "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 no válido o caducado"
          },
          "404": {
            "description": "El contacto solicitado no existe"
          }
        }
      }
    },
    "/Contact/{id}/Tag/{tagid}": {
      "put": {
        "tags": [
          "Contact"
        ],
        "operationId": "addTagToContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Añadir etiqueta a contacto",
        "description": "Asigna una etiqueta a un contacto. Si la etiqueta ya está asignada, la operación no produce ningún efecto.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID del contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "tagid",
            "in": "path",
            "required": true,
            "description": "ID de la etiqueta (de `GET /Tag`)",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5
          }
        ],
        "responses": {
          "200": {
            "description": "Etiqueta asignada correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Contacto o etiqueta no encontrados"
          }
        }
      },
      "delete": {
        "tags": [
          "Contact"
        ],
        "operationId": "removeTagFromContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Eliminar etiqueta de contacto",
        "description": "Elimina la asignación de una etiqueta de un contacto.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID del contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "tagid",
            "in": "path",
            "required": true,
            "description": "ID de la etiqueta",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5
          }
        ],
        "responses": {
          "200": {
            "description": "Etiqueta eliminada correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Contacto o etiqueta no encontrados"
          }
        }
      }
    },
    "/Contact/{id}/List/{listid}": {
      "put": {
        "tags": [
          "Contact"
        ],
        "operationId": "addContactToList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Añadir contacto a lista",
        "description": "Añade un contacto a una lista concreta.\nDevuelve HTTP 404 si el contacto había cancelado previamente la suscripción a esta lista.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID del contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "listid",
            "in": "path",
            "required": true,
            "description": "ID de la lista (de `GET /List`)",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "Contacto añadido a la lista correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Contacto, lista no encontrada, o el contacto había cancelado la suscripción a esta lista"
          }
        }
      },
      "delete": {
        "tags": [
          "Contact"
        ],
        "operationId": "removeContactFromList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Eliminar contacto de una lista",
        "description": "Elimina un contacto de una lista concreta. El contacto en sí no se elimina.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "UUID del contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "listid",
            "in": "path",
            "required": true,
            "description": "ID de la lista",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "Contacto eliminado de la lista correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Contacto o lista no encontrados"
          }
        }
      }
    },
    "/List": {
      "get": {
        "tags": [
          "List"
        ],
        "operationId": "getLists",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar las listas de contactos",
        "description": "Devuelve todas las listas de contactos de la cuenta.\nFiltra por nombre o fecha de creación. Ordena alfabéticamente con `sortByName=true`.\nIncluye el número total de contactos por lista con `includeTotalContacts=true`.\n",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filtrar por nombre de lista (coincidencia parcial)",
            "schema": {
              "type": "string"
            },
            "example": "newsletter"
          },
          {
            "name": "dateCreationFrom",
            "in": "query",
            "description": "Filtrar por fecha de creación (desde, ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "dateCreationTo",
            "in": "query",
            "description": "Filtrar por fecha de creación (hasta, ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "description": "Elementos por página — 5000 por defecto y como máximo",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "description": "Número de página, basado en 1",
            "schema": {
              "type": "integer"
            },
            "example": 1
          },
          {
            "name": "sortByName",
            "in": "query",
            "description": "Establece en `true` para ordenar alfabéticamente por nombre",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "includeTotalContacts",
            "in": "query",
            "description": "Establece en `true` para incluir el recuento `total_contacts` en cada resultado",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Listas devueltas correctamente",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": 101,
                    "name": "Newsletter subscribers",
                    "total_contacts": 4521,
                    "date_creation": "2025-01-15T10:30:00"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      },
      "post": {
        "tags": [
          "List"
        ],
        "operationId": "createList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Crear una lista de contactos",
        "description": "Crea una nueva lista de contactos. Máximo 1000 listas por cuenta.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListEntry"
              },
              "example": {
                "name": "Spring Campaign 2026"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lista creada correctamente",
            "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 no válido o caducado"
          },
          "406": {
            "description": "Se ha alcanzado el límite de listas (1000)"
          }
        }
      }
    },
    "/List/{id}": {
      "get": {
        "tags": [
          "List"
        ],
        "operationId": "getList",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Obtener una lista de contactos",
        "description": "Devuelve los detalles de una lista de contactos concreta.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID de la lista",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "Lista devuelta correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Lista no encontrada"
          }
        }
      },
      "put": {
        "tags": [
          "List"
        ],
        "operationId": "updateList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Actualizar una lista de contactos",
        "description": "Renombra una lista de contactos existente.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID de la 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 actualizada correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Lista no encontrada"
          }
        }
      },
      "delete": {
        "tags": [
          "List"
        ],
        "operationId": "deleteList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Eliminar una lista de contactos",
        "description": "Elimina una lista de contactos de forma permanente.\nLos contactos en sí no se eliminan: solo se elimina su pertenencia a esta lista.\nLas listas utilizadas por flujos de trabajo de automatización activos no pueden eliminarse.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID de la lista",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "Lista eliminada correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Lista no encontrada"
          },
          "406": {
            "description": "La lista está siendo utilizada por un flujo de trabajo de automatización activo"
          }
        }
      }
    },
    "/List/{id}/Contact": {
      "get": {
        "tags": [
          "List"
        ],
        "operationId": "getListContacts",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar contactos de una lista",
        "description": "Devuelve los contactos que pertenecen a una lista concreta.\nFiltra por correo electrónico o fecha de adición. Incluye etiquetas y pertenencias a listas con los indicadores correspondientes.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID de la lista",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          },
          {
            "name": "email",
            "in": "query",
            "description": "Filtrar por correo electrónico (coincidencia parcial)",
            "schema": {
              "type": "string"
            },
            "example": "@example.com"
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "description": "Elementos por página — 5000 por defecto y como máximo",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          },
          {
            "name": "sortByEmail",
            "in": "query",
            "description": "Ordenar alfabéticamente por correo electrónico",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "includeTags",
            "in": "query",
            "description": "Incluir las asignaciones de etiquetas en los resultados",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "includeLists",
            "in": "query",
            "description": "Incluir las pertenencias a listas en los resultados",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contactos devueltos correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Lista no encontrada o ningún contacto coincide con los criterios"
          }
        }
      }
    },
    "/List/{listId}/Unsubscribers": {
      "get": {
        "tags": [
          "List"
        ],
        "operationId": "getListUnsubscribers",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar los que han cancelado la suscripción",
        "description": "Devuelve los contactos que han cancelado la suscripción a una lista concreta.",
        "parameters": [
          {
            "name": "listId",
            "in": "path",
            "required": true,
            "description": "ID de la lista",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "Lista de cancelaciones de suscripción devuelta correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Lista no encontrada"
          }
        }
      }
    },
    "/Tag": {
      "get": {
        "tags": [
          "Tag"
        ],
        "operationId": "getTags",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar etiquetas",
        "description": "Devuelve todas las etiquetas de la cuenta.",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filtrar por nombre (coincidencia 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 devueltas correctamente",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": 5,
                    "name": "VIP customers"
                  },
                  {
                    "id": 6,
                    "name": "Newsletter openers"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      },
      "post": {
        "tags": [
          "Tag"
        ],
        "operationId": "createTag",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Crear una etiqueta",
        "description": "Crea una nueva etiqueta. Máximo 1000 etiquetas por cuenta.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagEntry"
              },
              "example": {
                "name": "VIP customers"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Etiqueta creada correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "406": {
            "description": "Se ha alcanzado el límite de etiquetas (1000)"
          }
        }
      }
    },
    "/Tag/{id}": {
      "get": {
        "tags": [
          "Tag"
        ],
        "operationId": "getTag",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Obtener una etiqueta",
        "description": "Devuelve una etiqueta concreta por su ID.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID de la etiqueta",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5
          }
        ],
        "responses": {
          "200": {
            "description": "Etiqueta devuelta correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Etiqueta no encontrada"
          }
        }
      },
      "put": {
        "tags": [
          "Tag"
        ],
        "operationId": "updateTag",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Actualizar una etiqueta",
        "description": "Renombra una 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 actualizada correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Etiqueta no encontrada"
          }
        }
      },
      "delete": {
        "tags": [
          "Tag"
        ],
        "operationId": "deleteTag",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Eliminar una etiqueta",
        "description": "Elimina una etiqueta de forma permanente. La etiqueta se elimina de todos los contactos.\nLas etiquetas utilizadas por flujos de trabajo de automatización activos no pueden eliminarse.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5
          }
        ],
        "responses": {
          "200": {
            "description": "Etiqueta eliminada correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Etiqueta no encontrada"
          },
          "406": {
            "description": "La etiqueta está siendo utilizada por un flujo de trabajo de automatización activo"
          }
        }
      }
    },
    "/Tag/{id}/Contact": {
      "get": {
        "tags": [
          "Tag"
        ],
        "operationId": "getTagContacts",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar contactos con una etiqueta",
        "description": "Devuelve los contactos a los que se les ha asignado una etiqueta concreta.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID de la 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 devueltos correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Etiqueta no encontrada"
          }
        }
      }
    },
    "/Field": {
      "get": {
        "tags": [
          "Field"
        ],
        "operationId": "listFields",
        "summary": "Listar campos personalizados",
        "description": "Devuelve todos los campos personalizados de contacto definidos para la cuenta.\nUsa los valores `id` devueltos al crear o actualizar contactos.\n",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filtrar por nombre de campo",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idFieldType",
            "in": "query",
            "description": "Filtrar por ID de 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 devueltos correctamente",
            "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 no válido o caducado"
          }
        }
      },
      "post": {
        "tags": [
          "Field"
        ],
        "operationId": "createField",
        "summary": "Crear un campo personalizado",
        "description": "Crea un nuevo campo personalizado de contacto.\nMáximo 50 campos por cuenta.\nUsa el `id` devuelto al asignar valores a los 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 creado correctamente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Field"
                }
              }
            }
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "406": {
            "description": "Se ha alcanzado el límite de campos (50)"
          }
        }
      }
    },
    "/Field/Types": {
      "get": {
        "tags": [
          "Field"
        ],
        "operationId": "listFieldTypes",
        "summary": "Listar los tipos de campo",
        "description": "Devuelve los identificadores de tipo de campo disponibles para usar al crear un campo personalizado.\nTipos habituales: `1` = texto, `2` = número, `3` = fecha.\n",
        "responses": {
          "200": {
            "description": "Tipos de campo devueltos correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Field/{id}": {
      "get": {
        "tags": [
          "Field"
        ],
        "operationId": "getField",
        "summary": "Obtener un campo personalizado",
        "description": "Devuelve la definición y los metadatos de un campo personalizado concreto.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID del campo",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Campo devuelto correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Campo no encontrado"
          }
        }
      },
      "put": {
        "tags": [
          "Field"
        ],
        "operationId": "updateField",
        "summary": "Actualizar un campo personalizado",
        "description": "Actualiza el nombre o la clave de personalización de un 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 actualizado correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Campo no encontrado"
          }
        }
      },
      "delete": {
        "tags": [
          "Field"
        ],
        "operationId": "deleteField",
        "summary": "Eliminar un campo personalizado",
        "description": "Elimina un campo personalizado de forma permanente y borra su valor de todos los contactos.\nEsta acción no se puede deshacer.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Campo eliminado correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Campo no encontrado"
          }
        }
      }
    },
    "/Campaign": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "listCrmCampaigns",
        "summary": "Listar campañas",
        "description": "Devuelve una lista paginada de las campañas de correo electrónico de la cuenta.",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 20
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Campañas devueltas correctamente",
            "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 no válido o caducado"
          }
        }
      }
    },
    "/Campaign/{id}": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaign",
        "summary": "Obtener una campaña",
        "description": "Devuelve la configuración y el resumen de estadísticas de una campaña concreta.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Campaña devuelta correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Campaña no encontrada"
          }
        }
      },
      "put": {
        "tags": [
          "Campaign"
        ],
        "operationId": "updateCrmCampaign",
        "summary": "Actualizar una campaña",
        "description": "Actualiza el título o la fecha de envío de una campaña programada.\nLas campañas solo pueden modificarse hasta **5 minutos antes** de la hora de envío programada.\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": "Campaña actualizada correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Campaña no encontrada"
          }
        }
      },
      "delete": {
        "tags": [
          "Campaign"
        ],
        "operationId": "deleteCrmCampaign",
        "summary": "Eliminar una campaña",
        "description": "Cancela y elimina permanentemente una campaña por su identificador.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Campaña eliminada correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Campaña no encontrada"
          }
        }
      }
    },
    "/Campaign/Scheduled": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "listScheduledCrmCampaigns",
        "summary": "Listar mis campañas programadas",
        "description": "Devuelve las campañas con `date_planned` en el futuro que aún no han sido\nenviadas. Ordenadas por `date_planned` de forma 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 campañas programadas",
            "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 no válido o caducado"
          }
        }
      }
    },
    "/Campaign/{id}/Cancel": {
      "post": {
        "tags": [
          "Campaign"
        ],
        "operationId": "cancelScheduledCrmCampaign",
        "summary": "Cancelar una campaña programada",
        "description": "Cancela una campaña programada en el futuro. Devuelve **409 Conflict** si la\ncampaña ya se ha enviado o no existe.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campaña cancelada",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "cancelled": 5001
                }
              }
            }
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "409": {
            "description": "Ya enviada o no encontrada"
          }
        }
      }
    },
    "/Campaign/{id}/Reschedule": {
      "patch": {
        "tags": [
          "Campaign"
        ],
        "operationId": "rescheduleCrmCampaign",
        "summary": "Reprogramar una campaña programada",
        "description": "Actualiza la fecha de envío prevista de una campaña programada. La nueva fecha\ndebe estar en el futuro y dentro de los 90 días.\n\n**Nombre del campo en el cuerpo:** la API acepta `date_planned` (snake_case, principal)\nasí como los alias heredados `PlanDate` y `scheduled_time`. Proporciona uno cualquiera\nde ellos: se utiliza el primero que no sea nulo.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Proporciona uno de `date_planned`, `PlanDate` o `scheduled_time`.",
                "properties": {
                  "date_planned": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Nombre del campo principal.",
                    "example": "2026-12-31T14:30:00"
                  },
                  "PlanDate": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Alias heredado aceptado por compatibilidad con versiones anteriores."
                  },
                  "scheduled_time": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Alias heredado aceptado por compatibilidad con versiones anteriores."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Campaña reprogramada",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "id": 5001,
                  "date_planned": "2026-12-31T14:30:00"
                }
              }
            }
          },
          "400": {
            "description": "Falta la fecha: proporciona uno de date_planned / PlanDate / scheduled_time"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "409": {
            "description": "Ya enviada",
            "not found": null,
            "or out of range": null
          }
        }
      }
    },
    "/Campaign/{id}/ByHour": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByHour",
        "summary": "Estadísticas por hora",
        "description": "Devuelve el desglose por hora de aperturas y clics de la campaña.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Estadísticas por hora devueltas correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Campaign/{id}/ByDay": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByDay",
        "summary": "Estadísticas por día",
        "description": "Devuelve el desglose diario de aperturas y clics de la campaña.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Estadísticas diarias devueltas correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Campaign/{id}/Openings": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignOpenings",
        "summary": "Listar las aperturas de correo electrónico",
        "description": "Devuelve una lista paginada de los contactos que han abierto el correo electrónico de la campaña.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          },
          {
            "name": "email",
            "in": "query",
            "description": "Filtrar por correo electrónico del destinatario",
            "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 aperturas devuelta correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Campaign/{id}/Clicks": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignClicks",
        "summary": "Listar los clics en enlaces",
        "description": "Devuelve una lista paginada de los contactos que han hecho clic en un enlace de la campaña.",
        "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 clics devuelta correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Campaign/{id}/HardBounces": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignHardBounces",
        "summary": "Listar los rebotes definitivos",
        "description": "Devuelve las direcciones de correo electrónico que han generado un rebote definitivo durante esta campaña.",
        "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 rebotes definitivos devuelta correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Campaign/{id}/Links": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignLinks",
        "summary": "Listar los enlaces rastreados",
        "description": "Devuelve los datos de rendimiento de clics de cada enlace rastreado de la campaña.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          },
          {
            "name": "link",
            "in": "query",
            "description": "Filtrar por URL del enlace (coincidencia 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": "Estadísticas de enlaces devueltas correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Campaign/{id}/OS": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByOS",
        "summary": "Estadísticas por SO / dispositivo",
        "description": "Devuelve el desglose de aperturas y clics por sistema operativo y tipo de dispositivo.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Estadísticas de SO devueltas correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Campaign/{id}/EmailClients": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByEmailClient",
        "summary": "Estadísticas por cliente de correo electrónico",
        "description": "Devuelve el desglose de aperturas por cliente de correo electrónico (Gmail, Outlook, Apple Mail…).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Estadísticas de clientes de correo electrónico devueltas correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Campaign/{id}/Countries": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByCountry",
        "summary": "Estadísticas por país",
        "description": "Devuelve la distribución geográfica de aperturas y clics.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Estadísticas por país devueltas correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Send/Mail": {
      "post": {
        "tags": [
          "Send"
        ],
        "operationId": "sendTransactionalEmailCrm",
        "summary": "Enviar correo electrónico transaccional",
        "description": "Envía un correo electrónico transaccional puntual a un único destinatario.\n\n**Destinatario:** proporciona `email` (dirección en línea) **O** `id_contact`\n(contacto del CRM), no ambos.\nUsar `id_contact` habilita la personalización automática con los valores de\nlos campos personalizados del contacto.\n\n**Contenido:** proporciona `body_html` (HTML en línea) **O** `id_message`\n(plantilla guardada), no ambos.\nSi se proporcionan ambos, `id_message` tiene prioridad.\n\n**Obligatorio:** `id_sender_email`, `subject` y uno de (`email` o `id_contact`),\ny uno de (`body_html` o `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": "Correo electrónico enviado o programado correctamente",
            "content": {
              "application/json": {
                "example": {
                  "message": "Successful operation",
                  "id_single_send": 8001
                }
              }
            }
          },
          "400": {
            "description": "Formato de correo electrónico no válido o falta un campo obligatorio"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Remitente, mensaje o contacto no encontrados; o el destinatario ha cancelado la suscripción",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "no_credits": {
                    "summary": "Créditos insuficientes",
                    "value": {
                      "Message": "Not enough credit available"
                    }
                  },
                  "unsubscribed": {
                    "summary": "Destinatario con suscripción cancelada",
                    "value": {
                      "Message": "Email address unsubscribed."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Error de validación (asunto demasiado largo, cuerpo y mensaje vacíos a la vez, etc.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/Send/{id}/Status": {
      "get": {
        "tags": [
          "Send"
        ],
        "operationId": "getTransactionalStatusCrm",
        "summary": "Obtener el estado de entrega de un correo transaccional",
        "description": "Devuelve el estado de entrega actual de un correo transaccional enviado con `POST /Send/Mail`. Pase el `id_single_send` que devolvió esa llamada.\n\nValores posibles: `delivered` (aceptado por el servidor del destinatario, véase `diag_code`), `bounced` (rechazado — `bounce_type: hard` dirección no válida o rechazo definitivo, `soft` rechazo tras nuevos intentos), `sent` (entregado al servidor de correo, aún sin respuesta), `scheduled` (aún no enviado, `date_planned` en el futuro) y `queued` (en espera de envío). `delivered` y `bounced` son finales: deje de consultar. Cada email tiene un estado final como máximo 48 horas después del envío; consulte cada 15 minutos como máximo (cada llamada cuenta en su cuota de la API) o utilice los webhooks `email.delivered` / `email.bounced`. Solo el cliente que envió el email puede leer su estado — cualquier otro id devuelve 404.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "El `id_single_send` devuelto por `POST /Send/Mail`.",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Estado de entrega actual",
            "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 no válido o caducado"
          },
          "404": {
            "description": "No se encontró ningún envío transaccional con este id para este cliente"
          }
        }
      }
    },
    "/Send/Campaign": {
      "post": {
        "tags": [
          "Send"
        ],
        "operationId": "sendCampaignCrm",
        "summary": "Enviar campaña de correo electrónico",
        "description": "Envía una campaña masiva de correo electrónico a una o varias listas de contactos.\n\n**Obligatorio:**\n- `id_message`: la plantilla que se va a enviar (de `GET /Message`)\n- `id_sender_email`: la dirección del remitente (de `GET /SenderEmail`)\n- `lists`: al menos una lista de contactos destinataria\n- `campaign_title`: etiqueta para el seguimiento de estadísticas\n\n**Segmento:** filtra opcionalmente a los destinatarios mediante un segmento\nguardado (`id_segment`). El segmento filtra los contactos dentro de las listas\nespecificadas.\n\n**Programación:** establece `date_planned` en una fecha/hora ISO 8601 futura.\nLas campañas pueden editarse o cancelarse hasta 5 minutos antes de la hora programada.\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": "Campaña programada o enviada correctamente",
            "content": {
              "application/json": {
                "example": {
                  "Message": "Successful operation. Campaigns can be edited or deleted until 5 minutes prior to send.",
                  "IdCampaign": 8001
                }
              }
            }
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "402": {
            "description": "Créditos insuficientes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Mensaje, remitente, lista o segmento no encontrados; o créditos insuficientes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Título de campaña vacío o demasiado largo (máx. 150 caracteres)"
          }
        }
      }
    },
    "/Send": {
      "get": {
        "tags": [
          "Send"
        ],
        "operationId": "getSendWaiting",
        "summary": "Listar envíos programados",
        "description": "Devuelve la lista de campañas de correo electrónico pendientes de envío (programadas, aún no procesadas).",
        "parameters": [
          {
            "name": "subjectMessage",
            "in": "query",
            "description": "Filtrar por asunto (coincidencia 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": "Envíos pendientes devueltos correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Send/{id}": {
      "delete": {
        "tags": [
          "Send"
        ],
        "operationId": "deleteSendWaiting",
        "summary": "Cancelar un envío programado",
        "description": "Cancela y elimina un envío de correo electrónico programado que aún no se haya procesado.\nNo se pueden cancelar envíos que se estén procesando en ese momento.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID del envío (de `GET /Send`)",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 3001
          }
        ],
        "responses": {
          "200": {
            "description": "Envío programado cancelado correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Envío programado no encontrado"
          }
        }
      }
    },
    "/Send/Mail/Scheduled": {
      "get": {
        "tags": [
          "Send"
        ],
        "operationId": "listScheduledCrmMails",
        "security": [
          {
            "OAuth2": [
              "email:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar los correos individuales programados",
        "description": "Devuelve los correos individuales enviados con `POST /Send/Mail` y una fecha futura que aún no han salido. Las campañas se listan con `GET /Campaign/Scheduled`.",
        "responses": {
          "200": {
            "description": "Correos programados devueltos",
            "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 no válido o caducado"
          }
        }
      }
    },
    "/Send/Mail/{id}/Cancel": {
      "post": {
        "tags": [
          "Send"
        ],
        "operationId": "cancelCrmMail",
        "security": [
          {
            "OAuth2": [
              "email:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Cancelar un correo individual programado",
        "description": "Cancela el correo mientras no haya salido.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id_single_send` devuelto por `POST /Send/Mail`",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Correo cancelado",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "cancelled": 880123
                }
              }
            }
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "409": {
            "description": "Envío no encontrado o ya realizado"
          }
        }
      }
    },
    "/Send/Mail/{id}/Reschedule": {
      "patch": {
        "tags": [
          "Send"
        ],
        "operationId": "rescheduleCrmMail",
        "security": [
          {
            "OAuth2": [
              "email:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Reprogramar un correo individual programado",
        "description": "Cambia la fecha de envío. La nueva fecha debe ser futura y estar dentro de 90 días. También se acepta `POST`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id_single_send` devuelto 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": "Nueva fecha de envío (ISO 8601)",
                    "example": "2026-12-15T09:00:00"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Correo reprogramado",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "id": 880123,
                  "date_planned": "2026-12-15T09:00:00"
                }
              }
            }
          },
          "400": {
            "description": "Falta la fecha"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "409": {
            "description": "Envío no encontrado, ya realizado o fecha fuera de los límites"
          }
        }
      }
    },
    "/SenderEmail": {
      "get": {
        "tags": [
          "Sender Addresses"
        ],
        "operationId": "listCrmSenderEmails",
        "summary": "Listar direcciones de remitente",
        "description": "Devuelve las direcciones de remitente validadas configuradas para la cuenta.",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Direcciones de remitente devueltas correctamente",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": 201,
                    "email": "newsletter@yourcompany.com",
                    "sender_name": "Your Company Newsletter",
                    "is_validated": true
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      },
      "post": {
        "tags": [
          "Sender Addresses"
        ],
        "operationId": "addCrmSenderEmail",
        "security": [
          {
            "OAuth2": [
              "email:templates:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Añadir una dirección de remitente",
        "description": "Añade la dirección con los mismos controles que la aplicación y le envía un correo de validación. Se puede utilizar una vez que se haga clic en el enlace de ese correo. Una cuenta puede tener hasta 25 remitentes, validados o pendientes.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "sender_name"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "Dirección del remitente",
                    "example": "news@example.com"
                  },
                  "sender_name": {
                    "type": "string",
                    "description": "Nombre mostrado a los destinatarios",
                    "example": "Example News"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Dirección añadida. `domain_has_mx` es false cuando el dominio no tiene registro MX.",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "confirmation_sent": true,
                  "domain_has_mx": true
                }
              }
            }
          },
          "400": {
            "description": "Falta `email` o `sender_name`"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "422": {
            "description": "Dirección rechazada: no válida, ya entre los remitentes de la cuenta, dominio de correo público, nombre prohibido o límite de 25 alcanzado. El mensaje indica el motivo."
          },
          "503": {
            "description": "No disponible temporalmente, inténtelo de nuevo"
          }
        }
      }
    },
    "/SenderEmail/{id}/ResendValidation": {
      "post": {
        "tags": [
          "Sender Addresses"
        ],
        "operationId": "resendCrmSenderValidation",
        "security": [
          {
            "OAuth2": [
              "email:templates:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Enviar de nuevo el correo de validación",
        "description": "Para una dirección que aún espera la validación.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id_sender_email` de `GET /SenderEmail`",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Correo enviado de nuevo",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "confirmation_sent": true
                }
              }
            }
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Remitente no encontrado"
          },
          "409": {
            "description": "Este remitente ya está validado"
          },
          "503": {
            "description": "No disponible temporalmente, inténtelo de nuevo"
          }
        }
      }
    },
    "/SenderEmail/{id}/Authentication": {
      "get": {
        "tags": [
          "Sender Addresses"
        ],
        "operationId": "getCrmSenderAuthentication",
        "security": [
          {
            "OAuth2": [
              "email:templates:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Comprobar el dominio de un remitente (SPF, DKIM, DMARC, MX)",
        "description": "Comprueba el dominio en directo en el DNS y devuelve los registros que hay que publicar. Solo lectura. El DKIM se activa en la aplicación: `dkim_settings_url` abre la pantalla correcta.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id_sender_email` de `GET /SenderEmail`",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Estado del dominio. `spf.recommended_record` se crea a partir de su registro actual: publíquelo solo si `spf.change_needed` es 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 no válido o caducado"
          },
          "404": {
            "description": "Remitente no encontrado"
          },
          "503": {
            "description": "No disponible temporalmente, inténtelo de nuevo"
          }
        }
      }
    },
    "/History/Email": {
      "get": {
        "tags": [
          "History"
        ],
        "operationId": "getCrmHistoryEmail",
        "summary": "Listar el historial de envíos de correo electrónico",
        "description": "Devuelve el registro de envíos de correo electrónico pasados de la cuenta.",
        "parameters": [
          {
            "name": "subjectMessage",
            "in": "query",
            "description": "Filtrar por asunto (coincidencia parcial)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idCampaign",
            "in": "query",
            "description": "Filtrar por ID de campaña",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "includeBodyHTML",
            "in": "query",
            "description": "Incluir el cuerpo HTML completo en la respuesta (más 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": "Historial de correo electrónico devuelto correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/History/SMS": {
      "get": {
        "tags": [
          "History"
        ],
        "operationId": "getCrmHistorySms",
        "summary": "Listar el historial de envíos de SMS",
        "description": "Devuelve el registro de envíos de SMS pasados de la cuenta.",
        "parameters": [
          {
            "name": "dateSend",
            "in": "query",
            "description": "Filtrar a partir de esta fecha (ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "dateSendEnd",
            "in": "query",
            "description": "Filtrar hasta esta fecha (ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "phoneNumber",
            "in": "query",
            "description": "Filtrar por teléfono del destinatario (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": "Historial de SMS devuelto correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Suppression/Bounces": {
      "get": {
        "tags": [
          "Suppression"
        ],
        "operationId": "listCrmBounces",
        "security": [
          {
            "OAuth2": [
              "email:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar los rebotes de la cuenta",
        "description": "Los más recientes primero. El total está en la cabecera `X-Total-Count`.",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "description": "`hard` o `soft`",
            "schema": {
              "type": "string",
              "enum": [
                "hard",
                "soft"
              ]
            }
          },
          {
            "name": "source",
            "in": "query",
            "description": "Origen del rebote: `list`, `contact`, `api` o `smtp`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "description": "Dirección exacta",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dateFrom",
            "in": "query",
            "description": "A partir de esta fecha",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "dateTo",
            "in": "query",
            "description": "Hasta esta fecha",
            "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": "Rebotes devueltos",
            "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` debe ser `hard` o `soft`"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Suppression/Bounces/{id}": {
      "delete": {
        "tags": [
          "Suppression"
        ],
        "operationId": "deleteCrmBounce",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Eliminar un rebote",
        "description": "`source` y `type` son obligatorios, tal como los devuelve `GET /Suppression/Bounces`: un `id` por sí solo no es único.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id` del rebote",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": true,
            "description": "`list`, `contact`, `api` o `smtp`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": true,
            "description": "`hard` o `soft`",
            "schema": {
              "type": "string",
              "enum": [
                "hard",
                "soft"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Rebote eliminado",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "id": 5521,
                  "restored": false
                }
              }
            }
          },
          "400": {
            "description": "Falta `source` o `type`"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Rebote no encontrado"
          }
        }
      }
    },
    "/Suppression/Bounces/{id}/Restore": {
      "post": {
        "tags": [
          "Suppression"
        ],
        "operationId": "restoreCrmBounce",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Volver a poner en sus listas un contacto con rebote duro",
        "description": "Solo para un rebote duro con el origen `contact`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id` del rebote",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contacto repuesto",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "id": 5521,
                  "restored": true
                }
              }
            }
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Rebote duro de un contacto no encontrado o dirección ya no activa"
          }
        }
      }
    },
    "/Suppression/Unsubscribers": {
      "get": {
        "tags": [
          "Suppression"
        ],
        "operationId": "listCrmUnsubscribers",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar las bajas de la cuenta",
        "description": "Los más recientes primero. El total está en la cabecera `X-Total-Count`.",
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "description": "Origen de la baja: `contact`, `list`, `smtp`, `api` o `api3`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "description": "Dirección exacta",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dateFrom",
            "in": "query",
            "description": "A partir de esta fecha",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "dateTo",
            "in": "query",
            "description": "Hasta esta fecha",
            "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": "Bajas devueltas",
            "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 no válido o caducado"
          }
        }
      },
      "post": {
        "tags": [
          "Suppression"
        ],
        "operationId": "addCrmUnsubscriber",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Añadir una baja",
        "description": "Sin `id_list`, la dirección se da de baja de toda la cuenta. Con `id_list`, solo de esa lista de bajas.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "Dirección que dar de baja",
                    "example": "stop@example.com"
                  },
                  "comment": {
                    "type": "string",
                    "description": "Comentario libre",
                    "example": "Asked by phone"
                  },
                  "id_list": {
                    "type": "integer",
                    "format": "int64",
                    "description": "Lista de bajas de la cuenta (opcional)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Hecho. `already_unsubscribed` es true cuando la dirección ya estaba dada de baja.",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "already_unsubscribed": false
                }
              }
            }
          },
          "400": {
            "description": "Se requiere una dirección válida"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "`id_list` no es una lista de bajas de esta cuenta"
          }
        }
      }
    },
    "/Suppression/Unsubscribers/{id}": {
      "delete": {
        "tags": [
          "Suppression"
        ],
        "operationId": "deleteCrmUnsubscriber",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Eliminar una baja",
        "description": "`source` es obligatorio, tal como lo devuelve `GET /Suppression/Unsubscribers`: un `id` por sí solo no es único.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id` de la baja",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": true,
            "description": "`contact`, `list`, `smtp`, `api` o `api3`",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Baja eliminada",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "removed": 9012
                }
              }
            }
          },
          "400": {
            "description": "`source` es obligatorio"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Baja no encontrada"
          }
        }
      }
    },
    "/Segment": {
      "get": {
        "tags": [
          "Segment"
        ],
        "operationId": "getSegments",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar segmentos",
        "description": "Devuelve todos los segmentos dinámicos guardados de la cuenta.",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Segmentos devueltos correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Segment/{id}/Contact": {
      "get": {
        "tags": [
          "Segment"
        ],
        "operationId": "getCrmSegmentContacts",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Listar los contactos de un segmento",
        "description": "Con `id_list`, devuelve solo los contactos de esa lista que entran en el segmento: los que alcanzaría una campaña lista + segmento. El total está en la cabecera `X-Total-Count`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID del 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 devueltos"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Segmento o lista no encontrados"
          }
        }
      }
    },
    "/Segment/{id}/Count": {
      "get": {
        "tags": [
          "Segment"
        ],
        "operationId": "countCrmSegmentContacts",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Contar los contactos de un segmento",
        "description": "Solo el número, para conocer de antemano el tamaño de un envío. Con `id_list`, cuenta solo los contactos de esa lista que entran en el segmento.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID del 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 no válido o caducado"
          },
          "404": {
            "description": "Segmento o lista no encontrados"
          }
        }
      }
    },
    "/Segment/{id}/contact/{contactId}/check": {
      "get": {
        "tags": [
          "Segment"
        ],
        "operationId": "checkContactInSegment",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Comprobar si un contacto coincide con un segmento",
        "description": "Comprueba si un contacto concreto (por GUID) coincide actualmente con las reglas\nde un segmento dado. Útil para activar lógica de automatización bajo demanda.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID del segmento",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 4001
          },
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "description": "GUID del contacto",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "11111111-2222-3333-4444-555555555555"
          }
        ],
        "responses": {
          "200": {
            "description": "Devuelve el resultado de la coincidencia"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Segmento o contacto no encontrados"
          }
        }
      }
    },
    "/FolderList": {
      "get": {
        "tags": [
          "Folders"
        ],
        "operationId": "getFolderLists",
        "summary": "Listar las carpetas de listas",
        "description": "Devuelve las carpetas utilizadas para organizar las listas de contactos.",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filtrar por nombre de carpeta (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": "Carpetas de listas devueltas correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/FolderList/{id}": {
      "get": {
        "tags": [
          "Folders"
        ],
        "operationId": "getFolderListById",
        "summary": "Obtener una carpeta de listas",
        "description": "Devuelve los detalles de una carpeta de listas por su ID.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID de la carpeta",
            "schema": {
              "type": "integer"
            },
            "example": 12
          }
        ],
        "responses": {
          "200": {
            "description": "Carpeta devuelta correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Carpeta no encontrada"
          }
        }
      }
    },
    "/Sms/SingleSMS": {
      "post": {
        "tags": [
          "SMS"
        ],
        "operationId": "sendSingleSmsCrm",
        "summary": "Enviar un único SMS",
        "description": "Envía un único SMS a un número de teléfono directamente.\nÚtil para mensajes transaccionales (OTP, confirmaciones de pedido, alertas).\nEl número de teléfono acepta `+41…`, `0041…` o `41…` con o sin separadores;\nconsulta **Formato del número de teléfono** en la descripción de la API para\nver las entradas aceptadas/rechazadas.\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 o programado correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "422": {
            "description": "Formato de número de teléfono no válido"
          }
        }
      }
    },
    "/Sms/Campaign": {
      "post": {
        "tags": [
          "SMS"
        ],
        "operationId": "sendSmsCampaignCrm",
        "summary": "Enviar campaña de SMS",
        "description": "Envía un mensaje SMS al campo `phone_number` de todos los contactos de una o varias listas.\nLos contactos sin número de teléfono se omiten.\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": "Campaña de SMS enviada o programada correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Sms": {
      "get": {
        "tags": [
          "SMS"
        ],
        "operationId": "getSmsSends",
        "summary": "Listar el historial de SMS",
        "description": "Devuelve el registro de envíos de SMS (tanto individuales como de campaña) de la cuenta.",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Historial de SMS devuelto correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Sms/{id}": {
      "delete": {
        "tags": [
          "SMS"
        ],
        "operationId": "deleteSmsSendCrm",
        "summary": "Cancelar un SMS programado",
        "description": "Cancela un envío de SMS programado pendiente.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID del envío de SMS",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5001
          }
        ],
        "responses": {
          "200": {
            "description": "SMS programado cancelado correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "404": {
            "description": "Envío de SMS no encontrado"
          }
        }
      }
    },
    "/Sms/Scheduled": {
      "get": {
        "tags": [
          "SMS"
        ],
        "operationId": "listScheduledSmsCrm",
        "summary": "Listar envíos de SMS programados",
        "description": "Devuelve los envíos de SMS con un `date_planned` futuro (aún no enviados), ordenados por fecha de forma ascendente.",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 100
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Envíos de SMS programados devueltos correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Sms/{id}/Cancel": {
      "post": {
        "tags": [
          "SMS"
        ],
        "operationId": "cancelScheduledSmsCrm",
        "summary": "Cancelar un SMS programado",
        "description": "Cancela un envío de SMS programado solo si aún no ha sido enviado.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID del envío de SMS",
            "schema": {
              "type": "integer"
            },
            "example": 5001
          }
        ],
        "responses": {
          "200": {
            "description": "SMS programado cancelado correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "409": {
            "description": "Envío de SMS no encontrado, no perteneciente al cliente o ya enviado"
          }
        }
      }
    },
    "/Sms/{id}/Reschedule": {
      "patch": {
        "tags": [
          "SMS"
        ],
        "operationId": "rescheduleScheduledSmsCrm",
        "summary": "Reprogramar un SMS programado",
        "description": "Actualiza la fecha de envío prevista de un SMS programado. La nueva fecha\ndebe estar en el futuro y dentro de los 90 días. Falla si el SMS ya ha sido enviado.\n\n**Nombre del campo en el cuerpo:** la API acepta `date_planned` (snake_case, principal)\nasí como los alias heredados `PlanDate` y `scheduled_time`. Proporciona uno cualquiera\nde ellos.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "ID del envío de SMS",
            "schema": {
              "type": "integer"
            },
            "example": 5001
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Proporciona uno de `date_planned`, `PlanDate` o `scheduled_time`.",
                "properties": {
                  "date_planned": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Nombre del campo principal.",
                    "example": "2026-05-10T14:00:00Z"
                  },
                  "PlanDate": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Alias heredado aceptado por compatibilidad con versiones anteriores."
                  },
                  "scheduled_time": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Alias heredado aceptado por compatibilidad con versiones anteriores."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SMS programado reprogramado correctamente"
          },
          "400": {
            "description": "Falta la fecha: proporciona uno de date_planned / PlanDate / scheduled_time"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "409": {
            "description": "Reprogramación fallida (no encontrado, ya enviado o fecha fuera de rango)"
          }
        }
      }
    },
    "/FolderMessage": {
      "get": {
        "tags": [
          "Folders"
        ],
        "operationId": "getFolderMessages",
        "summary": "Listar carpetas de mensajes",
        "description": "Devuelve las carpetas utilizadas para organizar las plantillas de mensajes de correo electrónico.",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filtrar por nombre de carpeta",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Carpetas de mensajes devueltas correctamente"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Import/Upload": {
      "post": {
        "tags": [
          "Import"
        ],
        "operationId": "uploadImportFile",
        "summary": "Subir un archivo de contactos (CSV / XLSX) para una importación masiva",
        "description": "Sube un archivo CSV o XLSX para importar contactos a una lista.\nEl archivo se almacena y se pone en cola para su procesamiento asíncrono: el\nendpoint devuelve un `import_job_id` de inmediato.\n\n**Tipo de solicitud:** `multipart/form-data`\n\n**Campos de formulario obligatorios:**\n- `id_list` (entero): ID de la lista de destino\n- `file` (binario): el archivo CSV o XLSX que se va a subir\n- `json_match_fields` (cadena, JSON): qué columna del archivo va a qué\n  campo del contacto. `email_position` = columna del e-mail, numerada\n  desde 0 (obligatorio), `phone_position` = columna del número de móvil\n  o `-1`, `fields` = ID de los demás campos (ver `GET /Field`),\n  `fields_position` = columna de cada uno de ellos, `update_fields` =\n  actualizar los contactos existentes. Correspondencia ausente o no\n  válida: respuesta `400`, no se crea ninguna importación.\n\n**Opcional:**\n- `webhook_url` (cadena): URL HTTPS llamada cuando finaliza el procesamiento.\n  Recibe una carga útil JSON con el estado final (éxito / fallo) y los\n  recuentos de filas importadas / omitidas / no válidas.\n\n**Formato de archivo:**\n- Codificado en `UTF-8`\n- La primera fila puede contener los encabezados (detección automática: una\n  primera fila sin ninguna dirección de e-mail se omite)\n- Las columnas se asignan a los campos mediante `json_match_fields`\n- Tamaño máximo de archivo: 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 de la lista de destino",
                    "example": 11088
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "Archivo CSV o XLSX (UTF-8, máx. 50 MB)"
                  },
                  "json_match_fields": {
                    "type": "string",
                    "description": "Correspondencia JSON entre las columnas del archivo y los campos del 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 invocada cuando finaliza el procesamiento",
                    "example": "https://example.com/hooks/mailpro-import"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Archivo aceptado y puesto en cola",
            "content": {
              "application/json": {
                "example": {
                  "result": "OK",
                  "import_job_id": 14,
                  "internal_name": "230575-20260420142615-contacts.csv"
                }
              }
            }
          },
          "400": {
            "description": "Ningún archivo subido, falta id_list, o json_match_fields ausente / no válido"
          },
          "401": {
            "description": "Token no válido o caducado"
          },
          "500": {
            "description": "Error del servidor o del almacenamiento"
          }
        }
      }
    },
    "/Webhook": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Servicio de gestión de webhooks (independiente del host principal de la API)"
        }
      ],
      "get": {
        "summary": "Listar webhooks",
        "description": "Devuelve todos los webhooks configurados en la cuenta autenticada.",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "Array de webhooks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Webhook"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      },
      "post": {
        "summary": "Crear un webhook",
        "description": "Crea una nueva suscripción a webhook. La respuesta contiene un `secret` que\n**solo se muestra una vez**: guárdalo de inmediato en el servidor. Úsalo para\nverificar la firma HMAC de todas las cargas útiles entrantes.\n\n**Límites**: máximo 20 webhooks por cuenta, 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": "Integración con Slack"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook creado: el `secret` solo se devuelve aquí",
            "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": "Integración con 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 no válida, lista de eventos no válida o límite excedido"
          },
          "401": {
            "description": "Token no válido o caducado"
          }
        }
      }
    },
    "/Webhook/{id}": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Servicio de gestión de webhooks"
        }
      ],
      "parameters": [
        {
          "in": "path",
          "name": "id",
          "required": true,
          "schema": {
            "type": "integer",
            "format": "int64"
          }
        }
      ],
      "get": {
        "summary": "Obtener un webhook",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "El webhook",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "404": {
            "description": "Webhook no encontrado"
          }
        }
      },
      "put": {
        "summary": "Actualizar un webhook",
        "description": "Actualización parcial: solo se modifican los campos enviados.",
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook actualizado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "400": {
            "description": "Carga útil no válida"
          },
          "404": {
            "description": "Webhook no encontrado"
          }
        }
      },
      "delete": {
        "summary": "Eliminar un webhook",
        "description": "Elimina permanentemente el webhook y todo su historial de entregas.",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "204": {
            "description": "Eliminado"
          },
          "404": {
            "description": "Webhook no encontrado"
          }
        }
      }
    },
    "/Webhook/{id}/test": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Servicio de gestión de webhooks"
        }
      ],
      "parameters": [
        {
          "in": "path",
          "name": "id",
          "required": true,
          "schema": {
            "type": "integer",
            "format": "int64"
          }
        }
      ],
      "post": {
        "summary": "Enviar un evento de prueba",
        "description": "Pone en cola un evento `ping` inmediato (o cualquier otro tipo de evento mediante\nel campo `eventType` del cuerpo) a tu punto de conexión. Útil para verificar la\naccesibilidad y la verificación de firma antes de depender de eventos reales.\n",
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "eventType": {
                    "type": "string",
                    "default": "ping",
                    "description": "Sobrescribe el tipo de evento (debe estar suscrito por este webhook). `ping` siempre funciona."
                  }
                }
              },
              "example": {
                "eventType": "ping"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Evento puesto en cola para su envío",
            "content": {
              "application/json": {
                "example": {
                  "deliveryId": 1234,
                  "eventId": "evt_8f3a9b2c4d1e5f6789abcdef01234567",
                  "eventType": "ping"
                }
              }
            }
          },
          "404": {
            "description": "Webhook no encontrado"
          }
        }
      }
    },
    "/Webhook/{id}/rotate-secret": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Servicio de gestión de webhooks"
        }
      ],
      "parameters": [
        {
          "in": "path",
          "name": "id",
          "required": true,
          "schema": {
            "type": "integer",
            "format": "int64"
          }
        }
      ],
      "post": {
        "summary": "Rotar el secreto HMAC",
        "description": "Genera un nuevo secreto HMAC e invalida el anterior de inmediato.\nEl nuevo secreto se devuelve **una sola vez** en la respuesta: guárdalo. Todas las\nentregas a partir de ese momento se firmarán con el nuevo secreto.\n\nUtiliza esta opción si sospechas que tu secreto ha sido comprometido.\n",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "Nuevo secreto (visible una sola vez)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "secret": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "secret": "whsec_XyzA9876BcdE5432FghI1098JklM7654NopQ"
                }
              }
            }
          },
          "404": {
            "description": "Webhook no encontrado"
          }
        }
      }
    },
    "/Webhook/{id}/deliveries": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Servicio de gestión 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 paginación (= último ID de entrega visto). Devuelve las entregas con `id < cursor`."
        }
      ],
      "get": {
        "summary": "Listar intentos de entrega",
        "description": "Devuelve el historial de intentos de envío de este webhook (retención de 30 días).\nÚtil para depuración: qué eventos se enviaron, qué códigos de estado se recibieron,\nrecuentos de reintentos, mensajes de error.\n",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "Array de intentos de entrega (los más recientes primero)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Webhook no encontrado"
          }
        }
      }
    },
    "/Webhook/{id}/deliveries/{deliveryId}/replay": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Servicio de gestión 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": "Reproducir una entrega",
        "description": "Pone en cola un nuevo intento de entrega con la misma carga útil que el original.\nÚtil para volver a desencadenar una entrega fallida tras corregir tu punto de conexión.\nSe crea una NUEVA fila `Webhook_Delivery` (el original se conserva con fines de\nauditoría). La reproducción sigue la política de reintentos estándar\n(10s → 1min → 5min → 30min → 2h → 6h → 24h, después se abandona).\n",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "202": {
            "description": "Reproducción puesta en cola",
            "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 o entrega no encontrados"
          }
        }
      }
    }
  }
}