{
  "openapi": "3.0.3",
  "info": {
    "title": "Mailpro™ CRM API v3",
    "version": "3",
    "description": "The Mailpro™ CRM API v3 lets you programmatically manage contacts, lists, tags,\ncustom fields, email templates, and campaigns — and send both bulk marketing\nemails and individual transactional emails, plus SMS.\n\n## Authentication\n\nAll endpoints (except `POST /token` and the OAuth endpoints) accept **three\nauthentication methods**. Use whichever fits your stack.\n\n- **OAuth 2.0** (authorization_code + PKCE, or client_credentials) — the\n  modern choice for third-party apps and integration platforms\n  (Zapier, Make, n8n) that act on a Mailpro user's behalf. Issues scoped,\n  revocable access tokens. **The only method that carries granular scopes\n  and that you can safely hand to a third party.**\n- **Bearer JWT** (legacy) — an HMAC-HS256 token from `POST /token`. Looks\n  like OAuth but is tied to a single account credential and is not scoped.\n- **IdClient + ApiKey** (legacy) — your client ID + API key in HTTP headers.\n\n> Legacy Bearer JWT and IdClient+ApiKey are **not scope-gated** — they keep\n> full access to every endpoint they could already reach. Only OAuth 2.0\n> access tokens are subject to the per-endpoint scope checks shown on each\n> operation below (a token missing the scope gets **403 `insufficient_scope`**).\n\n### Method 1 — OAuth 2.0 (recommended for integrations)\n\nMailpro runs **one** OAuth 2.0 authorization server for all of its APIs,\nserved at a single version-neutral surface: **`https://api.mailpro.com/oauth/`**.\nOne server, one token — the **same** access token works across CRM v3,\nEmail v2 and SMS. Which endpoints a token may call is governed by the\ntoken's **scopes**, not by the API base URL you call. There is no\nper-version OAuth endpoint (no `/v3/oauth`, `/v2/oauth` or `/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 (verify signatures) | `GET https://api.mailpro.com/oauth/.well-known/jwks` |\n| Discovery | `GET https://api.mailpro.com/oauth/.well-known/openid-configuration` |\n\nIssuer (the `iss` claim on every token): `https://api.mailpro.com/oauth/`.\n\n**Supported grants**\n\n- `authorization_code` **+ PKCE** (`code_challenge_method=S256` is\n  **mandatory**) — the \"Connect Mailpro\" browser consent flow. Returns an\n  access token **and** a rotating refresh token.\n- `client_credentials` — machine-to-machine, no user. Returns an access\n  token only.\n- `refresh_token` — exchange a refresh token for a fresh access/refresh\n  pair. **Refresh tokens rotate**: each use invalidates the old one.\n\n**Token lifetimes**\n\n| Token | TTL |\n|-------|-----|\n| Access token (RS256 JWT) | **20 minutes** |\n| Refresh token (sliding, single-use) | **30 days** |\n| Authorization code | **60 seconds** |\n\n**Authorization Code + PKCE — step by step**\n\n1. Generate `code_verifier` (43–128 random chars) and\n   `code_challenge = base64url(sha256(code_verifier))`.\n2. Redirect the user to:\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. The user approves on the Mailpro consent screen; Mailpro redirects back\n   with `?code=…&state=…`. Verify `state` (CSRF protection).\n4. Exchange the code for 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. Call any Mailpro API with `Authorization: Bearer ACCESS_TOKEN`.\n6. When it expires, refresh with `grant_type=refresh_token` and store the\n   **new** refresh token (the old one is now dead).\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\nThe same OAuth access token is accepted by the Email v2\n(`api.mailpro.com/v2`) and SMS (`api.mailpro.com/sms`) APIs too — authenticate\nonce, call any API. Full scope catalog: see **OAuth 2.0 scopes** below.\n\n### Test OAuth 2.0 in minutes — quickstart\n\nThe fastest way to try OAuth is the **client_credentials** grant\n(server-to-server, no browser, no user). You need a `client_id` /\n`client_secret` pair (ask your Mailpro contact, or create one in **Settings →\nAPI access**). Replace the placeholders below — **never commit a real\nsecret**.\n\n**1. Get an access token**\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. Call a resource endpoint with the token**\n\n```bash\ncurl https://api.mailpro.com/v3/List \\\n  -H \"Authorization: Bearer eyJhbGciOiJSUzI1Ni…\"\n```\n\nThe **same** token also works on Email v2 (`…/v2/…`) and SMS (`…/sms/…`) —\nas long as it carries the scope each endpoint requires. A token missing the\nrequired scope is rejected with **403 `insufficient_scope`**.\n\n**Interactive \"Connect Mailpro\" (authorization_code + PKCE)** — when you act\non a *user's* behalf (Zapier/Make/n8n, partner apps): redirect the user to\n`GET https://api.mailpro.com/oauth/authorize` with `response_type=code`,\n`client_id`, `redirect_uri`, `scope`, `state`, `code_challenge` and\n`code_challenge_method=S256`; the user consents; you exchange the returned\n`code` at `POST https://api.mailpro.com/oauth/token` with the matching\n`code_verifier`. Full walk-through above; scopes in **OAuth 2.0 scopes** below.\n\n> Tokens are **RS256 JWTs** — access tokens live **20 minutes**, refresh\n> tokens **30 days** (sliding, single-use: each refresh rotates the token).\n> Verify signatures against the JWKS at\n> `https://api.mailpro.com/oauth/.well-known/jwks` and validate the scope of\n> each request against the scope matrix below.\n\n### Method 2 — Bearer JWT (legacy)\n\n```\nAuthorization: Bearer <access_token>\n```\n\nGet a token via `POST /token` (see below). Valid for **30 days** by default;\nuse `grant_type=refresh_token` to renew without re-authenticating. Simpler to\nset up, but tied to a single account credential and **cannot** be scoped or\nhanded to a third party — prefer OAuth 2.0 for any installable integration.\n\n### Method 3 — IdClient + ApiKey (simpler)\n\nSend your client ID and API key as **HTTP headers** (preferred) or as\n**query parameters**:\n\n```\nIdClient: 12345\nApiKey:   a1b2c3d4-e5f6-7890-abcd-ef1234567890\n```\n\nSame credentials as the v2 / SMS APIs — copy-paste from\n**Settings → API access**. No token exchange step. Header form is recommended\nbecause URLs are logged by IIS / Cloudflare / proxies.\n\n### Token endpoint (password grant)\n```\nPOST /token\nContent-Type: application/x-www-form-urlencoded\n\ngrant_type=password&username=YOUR_USERNAME&password=YOUR_API_KEY\n```\n- `username` — your Mailpro username (email)\n- `password` — your API key (GUID), found in **Settings → API access**\n\n### client_credentials\n`POST /v3/token` does **not** accept `grant_type=client_credentials`. For\nserver-to-server access use the OAuth 2.0 endpoint\n`POST https://api.mailpro.com/oauth/token` with your `client_id` **and**\n`client_secret` (see the OAuth quickstart above).\n\n> The token issued by `POST /v3/token` is also valid on the Email v2 API\n> (`api.mailpro.com/v2`) and the SMS API (`api.mailpro.com/sms`). Authenticate\n> once, call any API.\n\n## OAuth 2.0 scopes\n\nWhen your app requests an OAuth access token it declares a set of **scopes** —\npermissions that define exactly what the token may do. The user sees a\nplain-language description of each requested scope on the consent screen.\nScopes are space-delimited in the `scope` parameter\n(`scope=contacts:read email:send`). Request only what you need.\n\nScopes follow `<resource>:<action>` and are **hierarchically independent** —\n`contacts:write` does **not** imply `contacts:read`; request both if needed.\n\n| Scope | Allows |\n|-------|--------|\n| `contacts:read` | View your contacts, lists, tags, segments and custom fields. |\n| `contacts:write` | Create, edit and delete your contacts, lists, tags and custom fields. |\n| `email:send` | Send transactional and campaign emails on your behalf. |\n| `email:read` | View delivery stats, opens, clicks and bounce history. |\n| `email:templates:read` | View your email templates (subject, HTML, plain-text body). |\n| `email:templates:write` | Create, edit and delete your email templates. |\n| `sms:send` | Send transactional and campaign SMS messages on your behalf. |\n| `sms:read` | View SMS credit balance and message history. |\n| `sms:contacts:read` | View your SMS phone book. |\n| `sms:contacts:write` | Create, edit and delete entries in your SMS phone book. |\n| `webhooks:manage` | Subscribe to, edit and remove webhook endpoints for your account. |\n| `account:read` | View your account credits, limits and subscription status. |\n\n### Scope enforcement per endpoint\n\nScope checks are **live** and enforced per endpoint for OAuth-issued tokens\n(legacy auth is exempt). Each operation in this reference lists its required\nscope under **Authorization**. Summary for the CRM v3 surface:\n\n| Endpoints | Required scope |\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`, segment membership check | `contacts:read` |\n| `POST /Contact`, `POST /Contact/Multiple`, `PUT`/`DELETE /Contact/{id}`, add/remove tag or list on a contact, `POST`/`PUT`/`DELETE /List`, `POST`/`PUT`/`DELETE /Tag` | `contacts:write` |\n\n> Endpoints in this spec that are **not** listed above (e.g. `GET /Field`,\n> campaigns, transactional send, SMS send, message templates, account info)\n> are reachable by any valid token today — they are not yet scope-gated on\n> the CRM v3 surface. The Email v2 and SMS APIs additionally gate their own\n> send / template / contacts / account endpoints (see those specs).\n\nScope rejection returns **403** with\n`{ \"error\": \"insufficient_scope\", \"required_scope\": \"…\" }` (RFC 6750 §3.1).\n\n## Data format\n\n- All request/response bodies use **JSON**\n- Property names are in **snake_case**\n- Contact IDs are **UUIDs**\n- List, Tag, and Field IDs are **integers (int64)**\n\n## Pagination\n\n**Same rule on the three Mailpro APIs** (Email v2, CRM v3, SMS): **5,000 items per page,\nby default and at most.**\n- `numberOfRecords` — items per page. Omitted, `0`, or above 5,000 = 5,000.\n- `pageNumber` — 1-based page number (default 1).\n- Both are accepted as query parameters **or** HTTP headers.\n- A full page means there may be more: request the next `pageNumber` until a page\n  comes back with fewer items.\n- Exception: messages and history with their HTML body (`includeBodyHTML=true`) return\n  10 per page by default.\n- An empty page, or a search that matches nothing, returns `200` with an empty list `[]`.\n  `404` means that the resource itself (list, tag, segment, campaign…) does not exist.\n\n**Unknown parameters** are ignored, never rejected. When a request carries a parameter\nthe API does not recognise, the response includes `X-Ignored-Parameters` (the names) and\n`X-Documentation` (a link to this page), so a typo does not go unnoticed.\n\n## Business error codes\n\nBusiness rule violations return HTTP **406** with a JSON body:\n```json\n{ \"Message\": \"Contact cannot be created. Email cannot be empty.\" }\n```\n\nCommon 406 reasons:\n- `Email cannot be empty` — email is required\n- `Email has an invalid format` — not a valid email address\n- `A contact already exist with the same Email` — email uniqueness violation\n- `The email address was unsubscribed permanently` — address blacklisted\n- `The email address has identified has a hard bounce` — address bounced\n- `The email address has identified has a spam trap` — spam trap detected\n- `At least one List is necessary` — contacts must belong to at least one list\n- `One or more lists have reached the contact limit` — 1M contacts per list max\n- `Invalid Phone Number` — phone number format error\n- `Invalid country code for Phone Number` — unsupported country prefix\n- `Length exceeded for Phone Number (12)` — phone number too long\n\n### Status codes returned\n\n| Code | Meaning |\n|------|---------|\n| `400` | Malformed request (bad JSON, message over the SMS part limit) |\n| `401` | Missing, invalid or expired credentials |\n| `402` | Not enough credits to perform the send |\n| `403` | Allowed by your credentials, but refused — account blocked for SMS sending |\n| `404` | The referenced object does not exist (contact, list, tag, field, campaign) |\n| `406` | Contact business rule violation (see the list above) |\n| `422` | Input rejected by validation, or an account limit reached |\n\n> **Changed:** some of these used to return `404`. Insufficient credits now\n> return **402**, validation errors and account limits return **422**, and an\n> account blocked for SMS returns **403**. `404` is now used only for \"this\n> object does not exist\", consistently with the SMS API. If your integration\n> branches on `404` for those cases, update it to the codes above.\n\n## Phone number format\n\nPhone numbers are normalized automatically. You can send them in many formats —\nspaces, dashes, dots, slashes, parentheses, underscores and non-breaking spaces\nare stripped before validation. Any other character (letters, apostrophes,\n`=`, `;`, etc.) makes the number invalid.\n\n| Valid                                          | Invalid                                            |\n|------------------------------------------------|----------------------------------------------------|\n| `+41794849980` — E.164 international           | `0612345678` — national format (leading `0`)       |\n| `0041 79 484 99 80` — 00-prefix + spaces       | `41'794'849'80` — apostrophes (not stripped)       |\n| `+33 6-12-34-56-78` — + with dashes            | `tel=0041794849980` — extra characters             |\n| `41794849980` — bare digits                    | `1234567` — too short (< 8 digits)                 |\n\n**Rules:**\n- Accepted prefixes: `+`, `00`, or no prefix\n- Length after normalization: **8 to 15 digits** (E.164 maximum)\n- First digit after stripping prefix must be **1–9** (not `0`)\n- Stripped separators: space, tab, dash `-`, dot `.`, slash `/`, parentheses `( )`, underscore `_`, NBSP\n- Anything else (letters, apostrophes, `=`, `;`, `+` inside the number, etc.) → rejected\n- Stored form in our database: bare digits, no prefix (e.g. `41794849980`)\n\n**v3 Contact specifics:** the `phone_number` field on a Contact is currently\nlimited to **12 digits** after normalization (vs 15 for the standalone SMS\nendpoints). Numbers from countries like Germany (13), Brazil (13) or Italy\nmobile (12–13) may be rejected on `/v3/Contact` with\n`Length exceeded for Phone Number (12)`. This legacy constraint may be\nrelaxed in a future release.\n\n## Limits\n\n| Resource | Limit |\n|----------|-------|\n| Tags per account | 1,000 |\n| Lists per account | 1,000 |\n| Custom fields per account | 50 |\n| Monthly API requests per account | 50,000 (5× subscription email credits) |\n\n## Delivery tracking (transactional emails)\n\nEvery **single** email you send (`POST /Send/Mail`, the Email API v2, the SMTP relay or an automation) gets an identifier. It can be followed until the recipient's server accepts or rejects it. Campaigns are not covered here: use the statistics instead.\n\n1. **Send**: the response of `POST /Send/Mail` contains `id_single_send`.\n2. **Get the result** (recommended): the `email.delivered` and `email.bounced` webhook events push the final status to your server as soon as it is known, with no polling at all.\n3. **Or poll**: `GET /Send/{id}/Status` returns the current status (table below).\n\n| `status` | Meaning | Final? |\n|---|---|---|\n| `queued` | Accepted, waiting to be sent. | no |\n| `scheduled` | Planned for later (`date_planned`). | no |\n| `sent` | Handed over by our servers, no answer from the recipient server yet. | no |\n| `delivered` | Accepted by the recipient server (`diag_code` = its SMTP reply). | **yes** |\n| `bounced` | Rejected. `bounce_type`: `hard` = invalid address or permanent refusal; `soft` = refused after retries (mailbox full, server unreachable…). | **yes** |\n| `not_found` | Unknown id, or an id that does not belong to your account. | **yes** |\n\n**Polling without looping**\n\n- Stop polling as soon as the status is **final**: it will not change any more.\n- First poll **5 minutes** after sending at the earliest: `delivered` appears about 5 minutes after the handover.\n- Then **one poll every 15 minutes at most**.\n- If you poll a non-final send before 15 minutes, you get the same answer with the `Retry-After` header (the number of seconds to wait), and the call does not count against your quota.\n- **At most 48 hours** after sending, every email has a final status: do not poll beyond that.\n- `scheduled`: no need to poll before `date_planned`.\n- **Every poll counts against your API request quota.** Following these rules, one send costs at most 192 requests (4 per hour for 48 hours), and far fewer in practice, since you stop at the final status. The same tracking by webhook costs a single event.\n\nStatuses are kept for **365 days**.\n\n## Webhooks\n\nMailpro™ can notify your server in real time when certain events occur on your\naccount (a contact is created, an email is delivered, a campaign finishes, etc.).\nStripe-style — your URL receives an HMAC-SHA256 signed POST as soon as the\nevent fires.\n\nEach event sent to your webhook counts as 1 request against your API request quota.\n\n### How it works\n\n1. **Subscribe** to one or more event types via `POST /Webhook`. The response\n   contains a `secret` shown **once** (Stripe / GitHub style) — store it.\n2. **Mailpro POSTs** a signed JSON payload to your URL as soon as an event matches.\n3. **Verify the signature** server-side with your `secret` (snippets below).\n4. **Respond 200–299** within 10 seconds. Otherwise we retry with exponential\n   backoff (10 s → 1 min → 5 min → 30 min → 2 h → 6 h → 24 h, then give up).\n\n> **⏱ Activation delay** — after you create, update or delete a webhook, the\n> change takes up to **10 minutes** to propagate across the Mailpro API\n> servers. During that window events may not yet match your new subscription\n> (or may still match a just-removed one). This is the time required to\n> refresh the in-memory subscription cache on every API instance.\n\n### Payload envelope\n\nAll events share a common envelope:\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| Field | Format | Description |\n|-------|--------|-------------|\n| `id` | `evt_` + 32 hex | Unique event ID — use as an idempotency key on your side. |\n| `type` | `<domain>.<verb>` | Canonical event type (e.g. `email.delivered`, `contact.created`). |\n| `created` | Unix seconds UTC | Included in the HMAC signature. |\n| `apiVersion` | `\"v3\"` | Which Mailpro API published the event. |\n| `account.id` | int | Your Mailpro client ID. |\n| `account.name` | string | Your account name (handy for client-side debug). |\n| `data` | object | Event payload — shape depends on `type` (see catalog below). |\n\n### Event catalog\n\n| Event | Trigger | `data` shape |\n|-------|---------|--------------|\n| `ping` | Test via `POST /Webhook/{id}/test` | `{ message }` |\n| `contact.created` | `POST /Contact` | `{ id, email, firstName, lastName, createdAt }` |\n| `contact.updated` | `PUT /Contact/{id}` | `{ id, email, firstName, lastName, updatedAt, changes }` |\n| `contact.deleted` | `DELETE /Contact/{id}` | `{ id, email, deletedAt }` |\n| `list.contact_added` | Contact added to list | `{ listId, listName, contactId, contactEmail, addedAt }` |\n| `list.contact_removed` | Contact removed from list | `{ listId, listName, contactId, contactEmail, removedAt }` |\n| `tag.applied` | Tag applied to contact | `{ tag, contactId, contactEmail, appliedAt }` |\n| `tag.removed` | Tag removed | `{ tag, contactId, contactEmail, removedAt }` |\n| `segment.matched` | Contact enters a segment | `{ segmentId, segmentName, contactId, contactEmail, matchedAt }` |\n| `email.processed` | Send accepted by pipeline | `{ messageId, email, campaignId, processedAt }` |\n| `email.delivered` | A **single** email was accepted by the recipient server — API v2/v3, SMTP or Automation (**not** campaigns). Emitted from the PowerMTA delivery feed. | `{ messageId, email, idSending, sendingType, diagCode, deliveredAt }` |\n| `email.opened` | Tracking pixel hit | `{ messageId, email, campaignId, openedAt, ip, userAgent }` |\n| `email.clicked` | Click on tracked link | `{ messageId, email, campaignId, clickedAt, url, ip, userAgent }` |\n| `email.bounced` | Hard or soft bounce | `{ messageId, email, campaignId, bouncedAt, bounceType, reason }` |\n| `email.spam_report` | FBL complaint | `{ messageId, email, campaignId, reportedAt }` |\n| `email.unsubscribed` | Opt-out link clicked | `{ messageId, email, campaignId, unsubscribedAt }` |\n| `email.dropped` | Refused before send | `{ messageId, email, campaignId, droppedAt, reason }` |\n| `campaign.started` | Dispatch begins | `{ campaignId, name, totalRecipients, startedAt }` |\n| `campaign.finished` | All recipients processed | `{ campaignId, name, totalSent, totalFailed, finishedAt }` |\n| `import.started` | Import job started | `{ importId, fileName, totalRows, startedAt }` |\n| `import.progress` | At 25/50/75% | `{ importId, fileName, processedRows, totalRows, progressPct }` |\n| `import.completed` | Import OK | `{ importId, fileName, totalRows, addedCount, updatedCount, errorCount, completedAt }` |\n| `import.failed` | Fatal error during import | `{ importId, fileName, errorMessage, failedAt }` |\n\nSubscribe to all events of this API by passing `[\"*\"]` in the `events` array.\n\n### HMAC signature\n\nEvery POST carries an `X-Mailpro-Signature` header:\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\nTo verify:\n1. Extract `t` and `v1` from the header.\n2. **Reject** if `|now - t| > 300` (5-minute replay protection window).\n3. Compute `expected = hex(HMAC_SHA256(secret, t + \".\" + raw_body))`.\n4. **Constant-time compare** `v1` with `expected` (`crypto.timingSafeEqual`,\n   `hash_equals`, `CryptographicOperations.FixedTimeEquals`) — never `==`.\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### Retry and auto-disable\n\n| Attempt | Delay before next try |\n|---------|----------------------|\n| 1 → 2 | 10 seconds |\n| 2 → 3 | 1 minute |\n| 3 → 4 | 5 minutes |\n| 4 → 5 | 30 minutes |\n| 5 → 6 | 2 hours |\n| 6 → 7 | 6 hours |\n| 7 | abandoned (delivery marked `failed`) |\n\nAfter **10 consecutive failures** on the same webhook, it is **automatically\ndisabled** (`isActive: false`) and you receive a notification email. Re-enable\nit with `PUT /Webhook/{id}` once your endpoint is fixed.\n\n### Success = HTTP 2xx\n\nWe treat your webhook as successful only if you return a status in `200–299`.\nAny 3xx/4xx/5xx or a timeout (> 10 s) counts as a failure and triggers retries.\nIf your endpoint is permanently dead, return `410 Gone` explicitly — retries\nstill happen but the failure reason is clearer in your delivery log.\n\n### Limits\n\n| Limit | Value |\n|-------|-------|\n| Webhooks per account | 20 |\n| Subscribed events per webhook | 50 |\n| Max payload size | 256 KB |\n| HTTP timeout per attempt | 10 seconds |\n| Delivery history retention | 30 days |\n"
  },
  "servers": [
    {
      "url": "https://api.mailpro.com/v3",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "IdClientHeader": [],
      "ApiKeyHeader": []
    },
    {
      "IdClientQuery": [],
      "ApiKeyQuery": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Bearer JWT token obtained from `POST /token`.\nPass it as: `Authorization: Bearer <token>`\n"
      },
      "OAuth2": {
        "type": "oauth2",
        "description": "Mailpro OAuth 2.0 — the modern way for third-party apps and integration\nplatforms (Zapier, Make, n8n) to act on a Mailpro user's behalf with\nscoped, revocable access tokens.\n\n- **authorizationCode + PKCE** (S256, mandatory for public clients) — the\n  \"Connect Mailpro\" consent flow. Issues an access token + a rotating\n  refresh token.\n- **clientCredentials** — machine-to-machine, no user involved.\n\nOne authorization server for all APIs, at the version-neutral surface\n`https://api.mailpro.com/oauth/` (issuer / `iss` claim\n`https://api.mailpro.com/oauth/`). Access tokens are RS256 JWTs\n(verify against the JWKS at `https://api.mailpro.com/oauth/.well-known/jwks`);\nTTL **20 minutes**. Refresh tokens are valid **30 days** (sliding) and\n**single-use** (rotated on every refresh). Authorization codes live\n**60 seconds**.\n\nThe same OAuth access token is accepted by all three Mailpro APIs\n(CRM v3, Email v2, SMS) — authenticate once, call any API; access is\ngoverned by the token's scopes, not the API base URL. Legacy auth\n(Bearer JWT / IdClient+ApiKey) keeps working unchanged and is NOT\nscope-gated.\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": "Your numeric Mailpro client ID — preferred transport (HTTP header).\nURLs are logged by web servers, Cloudflare and proxies, so query-string\ncredentials can leak into access logs. Headers are not logged.\n"
      },
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "ApiKey",
        "description": "Your API key (GUID) — preferred transport (HTTP header)."
      },
      "IdClientQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "IdClient",
        "description": "Your numeric Mailpro client ID — legacy, kept for backward compatibility.\nSwitch to the `IdClient` header when you can.\n"
      },
      "ApiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "ApiKey",
        "description": "Your API key (GUID) — legacy, kept for backward compatibility."
      }
    },
    "schemas": {
      "TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string",
            "description": "JWT access token — use in all subsequent requests as `Authorization: Bearer <token>`"
          },
          "token_type": {
            "type": "string",
            "example": "bearer"
          },
          "expires_in": {
            "type": "integer",
            "description": "Token lifetime in seconds",
            "example": 31535999
          },
          "userName": {
            "type": "string",
            "description": "Client ID of the authenticated user",
            "example": "12345"
          },
          "refresh_token": {
            "type": "string",
            "description": "Token to obtain a new access token without re-authenticating"
          },
          "application": {
            "type": "string",
            "description": "Integration name (for third-party grants only)",
            "nullable": true
          }
        }
      },
      "Contact": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Unique identifier for the contact (UUID)",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "email": {
            "type": "string",
            "description": "Contact's email address",
            "example": "alice@example.com"
          },
          "phone_number": {
            "type": "string",
            "description": "Phone number — accepts `+41…`, `0041…` or `41…` with or without separators. See **Phone number format** in the API description.",
            "example": "+41791234567"
          },
          "tags": {
            "type": "array",
            "description": "Tags assigned to this contact (included when `includeTags=true`)",
            "items": {
              "$ref": "#/components/schemas/TagRef"
            }
          },
          "lists": {
            "type": "array",
            "description": "Lists this contact belongs to (included when `includeLists=true`)",
            "items": {
              "$ref": "#/components/schemas/ListRef"
            }
          },
          "custom_fields": {
            "type": "array",
            "description": "Custom field values for this contact",
            "items": {
              "$ref": "#/components/schemas/FieldValue"
            }
          }
        }
      },
      "ContactEntry": {
        "type": "object",
        "required": [
          "email",
          "lists"
        ],
        "properties": {
          "email": {
            "type": "string",
            "description": "Contact's email address (required, must be unique)",
            "example": "alice@example.com"
          },
          "phone_number": {
            "type": "string",
            "description": "Phone number — accepts `+41…`, `0041…` or `41…` with or without separators.\nMust start with a valid country code. Max 12 digits on Contact (see\n**Phone number format** in the API description for full rules).\n",
            "example": "+41791234567"
          },
          "lists": {
            "type": "array",
            "description": "Lists to assign this contact to. **At least one list is required.**",
            "items": {
              "$ref": "#/components/schemas/ListRef"
            },
            "example": [
              {
                "id": 101
              }
            ]
          },
          "tags": {
            "type": "array",
            "description": "Tags to assign to this contact (optional)",
            "items": {
              "$ref": "#/components/schemas/TagRef"
            },
            "example": [
              {
                "id": 5
              }
            ]
          },
          "custom_fields": {
            "type": "array",
            "description": "Custom field values for this contact (optional)",
            "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": "Tag ID (from `GET /Tag`)",
            "example": 5
          }
        }
      },
      "ListRef": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "List ID (from `GET /List`)",
            "example": 101
          }
        }
      },
      "FieldValue": {
        "type": "object",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Custom field ID (from `GET /Field`)",
            "example": 1
          },
          "value": {
            "type": "string",
            "description": "Value to store for this field. Type must match the field's type.",
            "example": "Alice"
          }
        }
      },
      "List": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Unique identifier for the list",
            "example": 101
          },
          "name": {
            "type": "string",
            "description": "Display name of the list",
            "example": "Newsletter subscribers"
          },
          "total_contacts": {
            "type": "integer",
            "description": "Total number of contacts in the list (included when `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": "Display name for the list",
            "example": "Newsletter subscribers — France"
          }
        }
      },
      "Tag": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Unique identifier for the tag",
            "example": 5
          },
          "name": {
            "type": "string",
            "description": "Display name of the tag",
            "example": "VIP customers"
          }
        }
      },
      "TagEntry": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Display name for the tag",
            "example": "VIP customers"
          }
        }
      },
      "Field": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Unique identifier for the custom field",
            "example": 1
          },
          "name": {
            "type": "string",
            "description": "Internal name of the field",
            "example": "first_name"
          },
          "personalisation_key": {
            "type": "string",
            "description": "The variable name used for personalization in email templates (e.g. `{{first_name}}`)",
            "example": "first_name"
          },
          "field_type": {
            "type": "string",
            "description": "Data type of the field",
            "enum": [
              "text",
              "number",
              "date"
            ],
            "example": "text"
          }
        }
      },
      "FieldEntry": {
        "type": "object",
        "required": [
          "name",
          "id_field_type",
          "personalize_key"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Display name of the field",
            "example": "First name"
          },
          "id_field_type": {
            "type": "integer",
            "description": "Field type ID (get available types from `GET /Field/Types`):\n- `1` = text\n- `2` = integer\n- `3` = date and time\n- `4` = yes / no\n- `12` = decimal\n",
            "example": 1
          },
          "personalize_key": {
            "type": "string",
            "description": "Template variable key, used to insert the field in a message",
            "example": "first_name"
          },
          "sort": {
            "type": "integer",
            "minimum": 0,
            "maximum": 255,
            "description": "Display order (0 to 255). Required when updating a field (`PUT /Field/{id}`).",
            "example": 255
          }
        }
      },
      "CampaignEntry": {
        "type": "object",
        "properties": {
          "campaign_title": {
            "type": "string",
            "description": "Display name for the campaign",
            "example": "Spring Newsletter April 2026"
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Scheduled send time (ISO 8601). Omit to send immediately.",
            "example": "2026-04-20T09:00:00"
          }
        }
      },
      "SendEmailRequest": {
        "type": "object",
        "description": "Transactional email request. Provide **either** `email` (inline address) **or**\n`id_contact` (CRM contact) — not both.\nProvide **either** `body_html` (inline HTML) **or** `id_message` (saved template) — not both.\n",
        "properties": {
          "id_sender_email": {
            "type": "integer",
            "description": "ID of the sender email address (from `GET /SenderEmail`). Required.",
            "example": 201
          },
          "email": {
            "type": "string",
            "description": "Recipient email address (inline). Provide this **OR** `id_contact`.\nIf both are given, `id_contact` takes precedence.\n\n**Local part** (before `@`) accepts letters, digits and `. _ - +` —\nincluding RFC 5322 sub-addressing (`user+tag@domain`, e.g. Gmail,\nFastmail, Proton). Other special characters RFC 5322 permits\n(e.g. `! # $ % & ' * / = ? ^ { | } ~`) and quoted local parts are\n**not** accepted.\n",
            "example": "alice@example.com"
          },
          "id_contact": {
            "type": "string",
            "format": "uuid",
            "description": "Recipient CRM contact ID. Provide this **OR** `email`.\nUsing `id_contact` enables personalization with the contact's custom field values.\n",
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          "subject": {
            "type": "string",
            "description": "Email subject line (max 255 characters). Required.",
            "example": "Your order #12345 has been shipped"
          },
          "body_html": {
            "type": "string",
            "description": "Inline HTML content. Provide this **OR** `id_message`.\nIf both are given, `id_message` takes precedence.\n",
            "example": "<p>Hello {{first_name}}, your order has shipped!</p>"
          },
          "id_message": {
            "type": "integer",
            "description": "ID of a saved message template (from `GET /Message`).\nProvide this **OR** `body_html`.\n",
            "example": 501
          },
          "reply_to": {
            "type": "string",
            "description": "Reply-to address (defaults to the sender address)",
            "example": "support@yourcompany.com"
          },
          "fields": {
            "type": "array",
            "description": "Custom personalization fields (only used with inline `email`, not with `id_contact`)",
            "items": {
              "$ref": "#/components/schemas/FieldValue"
            }
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Schedule the send for a future time. Omit to send immediately.",
            "example": "2026-04-15T14:00:00"
          },
          "attachments": {
            "type": "array",
            "maxItems": 5,
            "description": "Optional file attachments (max **5**). Each item is an object with a `filename` and\nBase64-encoded `content`. Limits: **10 MB per file** and **15 MB total**, measured after\nBase64 decoding. Accepted types: PDF, Word, Excel, PowerPoint, images (JPG, PNG, GIF,\nWebP), ZIP, CSV, TXT — executable and script files are rejected. **Billing:** the send keeps its usual 1 credit, plus **1 credit per 512 KB of\nattachments beyond the first 512 KB** (attachments up to ~512 KB add nothing).\n",
            "items": {
              "$ref": "#/components/schemas/EmailAttachment"
            }
          },
          "keep_in_my_files": {
            "type": "boolean",
            "default": false,
            "description": "`false` (default): the attachments are used for this send only — they are stored outside\nyour account, do not count toward your storage quota and are deleted automatically after\n35 days. `true`: the attachments are also kept in your account's files (folder\n`Secure/Files/Sending/api`) and count toward your storage quota.\n",
            "example": false
          }
        }
      },
      "EmailAttachment": {
        "type": "object",
        "required": [
          "filename",
          "content"
        ],
        "description": "A single email file attachment with Base64-encoded content.",
        "properties": {
          "filename": {
            "type": "string",
            "description": "Original file name shown to the recipient, e.g. \"invoice.pdf\".",
            "example": "invoice.pdf"
          },
          "content": {
            "type": "string",
            "format": "byte",
            "description": "File content, Base64-encoded.",
            "example": "JVBERi0xLjQKJVBERi0xLjQK..."
          }
        }
      },
      "SendCampaignRequest": {
        "type": "object",
        "required": [
          "id_message",
          "id_sender_email",
          "lists",
          "campaign_title"
        ],
        "properties": {
          "id_message": {
            "type": "integer",
            "description": "ID of the message template to use (from `GET /Message`)",
            "example": 501
          },
          "id_sender_email": {
            "type": "integer",
            "description": "ID of the sender address (from `GET /SenderEmail`)",
            "example": 201
          },
          "id_email_reply_to": {
            "type": "integer",
            "description": "ID of the reply-to sender address (from `GET /SenderEmail`). Must be one of your account's sender addresses, otherwise the request is rejected. Defaults to `id_sender_email`.",
            "example": 201
          },
          "lists": {
            "type": "array",
            "description": "Target contact lists (at least one required)",
            "items": {
              "$ref": "#/components/schemas/ListRef"
            },
            "example": [
              {
                "id": 101
              }
            ]
          },
          "id_segment": {
            "type": "integer",
            "description": "Filter recipients by a saved segment (optional)",
            "example": 7
          },
          "campaign_title": {
            "type": "string",
            "description": "Display name for this campaign (max 150 characters)",
            "example": "Spring Newsletter April 2026"
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Scheduled send time (ISO 8601). Omit to send immediately.",
            "example": "2026-04-20T09:00:00"
          },
          "id_automation": {
            "type": "integer",
            "description": "Link this send to an automation workflow (optional)",
            "example": 3
          }
        }
      },
      "SmsSingleRequest": {
        "type": "object",
        "required": [
          "phone_number",
          "message"
        ],
        "properties": {
          "phone_number": {
            "type": "string",
            "description": "Recipient phone number — accepts `+41…`, `0041…` or `41…` with or without separators. See **Phone number format** in the API description.",
            "example": "+41791234567"
          },
          "message": {
            "type": "string",
            "description": "SMS message text (max 160 chars for standard encoding)",
            "example": "Your verification code is 9473. Valid for 10 minutes."
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Schedule the send. Omit to send immediately.",
            "example": "2026-04-15T14:00:00"
          }
        }
      },
      "SmsCampaignRequest": {
        "type": "object",
        "required": [
          "lists",
          "message"
        ],
        "properties": {
          "lists": {
            "type": "array",
            "description": "Target contact lists (uses each contact's phone_number field)",
            "items": {
              "$ref": "#/components/schemas/ListRef"
            },
            "example": [
              {
                "id": 101
              }
            ]
          },
          "message": {
            "type": "string",
            "description": "SMS message text",
            "example": "Flash sale today — 20% off all products. Shop at mailpro.com"
          },
          "date_planned": {
            "type": "string",
            "format": "date-time",
            "description": "Schedule the send. Omit to send immediately.",
            "example": "2026-04-20T10:00:00"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "Message": {
            "type": "string",
            "description": "Human-readable error message",
            "example": "Contact cannot be created. Email cannot be empty."
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Webhook ID (Mailpro)",
            "example": 42
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "HTTPS endpoint that receives events",
            "example": "https://example.com/hooks/mailpro"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Subscribed event types",
            "example": [
              "email.delivered",
              "email.bounced",
              "contact.created"
            ]
          },
          "description": {
            "type": "string",
            "nullable": true,
            "description": "Free-form description (your own reference)",
            "example": "Slack integration"
          },
          "isActive": {
            "type": "boolean",
            "description": "When `false`, no event is delivered. Auto-disabled after 10 consecutive failures.",
            "example": true
          },
          "consecutiveFailures": {
            "type": "integer",
            "description": "Number of consecutive failed delivery attempts (resets on success)",
            "example": 0
          },
          "autoDisabledAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Set when `isActive` was flipped to `false` automatically"
          },
          "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": "HTTPS endpoint (HTTP refused in production)",
            "example": "https://example.com/hooks/mailpro"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "maxItems": 50,
            "description": "List of event types to subscribe to (see catalog above). Use `[\"*\"]` to subscribe to all events of this API.",
            "example": [
              "email.delivered",
              "email.bounced",
              "contact.created"
            ]
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "description": "Free-form description for your reference",
            "example": "Slack integration"
          }
        }
      },
      "WebhookCreateResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Webhook"
          },
          {
            "type": "object",
            "properties": {
              "secret": {
                "type": "string",
                "description": "HMAC secret. **Shown once here, never again.** Store it server-side — used to sign every payload sent to your URL.",
                "example": "whsec_AbcD1234EfgH5678IjkL9012MnoP3456QrsT"
              }
            }
          }
        ]
      },
      "WebhookUpdate": {
        "type": "object",
        "description": "Partial update — every field is optional.",
        "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": "Set `true` to re-enable an auto-disabled webhook once your endpoint is fixed."
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "Delivery attempt ID"
          },
          "eventId": {
            "type": "string",
            "description": "Event UUID (= the `id` field in the payload your endpoint received)",
            "example": "evt_8f3a9b2c4d1e5f6789abcdef01234567"
          },
          "eventType": {
            "type": "string",
            "example": "email.delivered"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "success",
              "retrying",
              "failed"
            ],
            "description": "Current state of this delivery"
          },
          "statusCode": {
            "type": "integer",
            "nullable": true,
            "description": "HTTP status code received from your endpoint (`null` if not yet attempted or on timeout)",
            "example": 200
          },
          "responseTimeMs": {
            "type": "integer",
            "nullable": true,
            "example": 142
          },
          "attemptCount": {
            "type": "integer",
            "description": "Number of attempts so far (1–7, then permanently failed)"
          },
          "lastError": {
            "type": "string",
            "nullable": true,
            "description": "First 2 KB of the response body or exception message when an attempt fails"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "nextAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the next retry will happen — `null` if delivery succeeded or was permanently abandoned."
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Authentication",
      "description": "Obtain a Bearer JWT token to authenticate all other API calls.\nThe token endpoint accepts multiple grant types: `password`, `refresh_token`,\nand third-party integrations (`zapier`, `automateio`, `microsoft-flow`). For client_credentials use `/oauth/token`.\n"
    },
    {
      "name": "Account",
      "description": "Retrieve account profile, credit balance, and subscription details."
    },
    {
      "name": "Contact",
      "description": "Full CRUD for CRM contacts. Contacts store an email address, optional phone number,\ncustom field values, and belong to one or more **Lists**. They can also be labelled with **Tags**.\nContact IDs are UUIDs.\n"
    },
    {
      "name": "List",
      "description": "Manage contact lists. Lists group contacts for campaigns and segmentation.\nMaximum 1,000 lists per account (same limit on the three Mailpro APIs and in the app). Maximum 1,000,000 contacts per list.\n"
    },
    {
      "name": "Tag",
      "description": "Manage tags for contact labelling and segmentation.\nTags are flat labels (e.g. \"VIP\", \"Churned\", \"B2B\").\nMaximum 1,000 tags per account.\n"
    },
    {
      "name": "Field",
      "description": "Manage custom contact fields. Fields let you store additional data on each contact\n(e.g. first name, city, loyalty tier). Maximum 50 fields per account.\nField types: `text`, `number`, `date`.\n"
    },
    {
      "name": "Message",
      "description": "Manage HTML email templates. Templates store the subject, HTML body,\nand unsubscribe link settings. Reuse templates across multiple campaigns.\n"
    },
    {
      "name": "Campaign",
      "description": "View campaign statistics and manage scheduled campaigns.\nCampaigns can be edited or deleted until 5 minutes before the scheduled send time.\n"
    },
    {
      "name": "Send",
      "description": "Send bulk email campaigns (to contact lists) and individual transactional emails.\nCampaigns can be scheduled or sent immediately.\n"
    },
    {
      "name": "Sender Addresses",
      "description": "Manage validated sender email addresses. Only verified addresses can be used as `From`.\n"
    },
    {
      "name": "History",
      "description": "Browse the log of past email and SMS sends."
    },
    {
      "name": "Segment",
      "description": "Manage dynamic segments — saved filter rules that automatically select contacts\nbased on field values, behavior, or list membership.\n"
    },
    {
      "name": "SMS",
      "description": "Send SMS messages to individual contacts or to entire contact lists.\nUses each contact's `phone_number` field.\n"
    },
    {
      "name": "Folders",
      "description": "Organize message templates into folders."
    },
    {
      "name": "Import",
      "description": "Upload a CSV or XLSX file to import contacts into a list in bulk.\nThe file is queued for asynchronous processing — track completion via\nthe `import_job_id` returned or via the optional `webhook_url` callback.\n"
    },
    {
      "name": "Webhooks",
      "description": "Subscribe your server to real-time event notifications. Mailpro POSTs an\nHMAC-signed JSON payload to your URL as soon as the subscribed event happens.\nSee the \"Webhooks\" section in the introduction for the full event catalog,\npayload envelope, signature verification snippets, and retry policy.\n"
    },
    {
      "name": "Suppression",
      "description": "Bounces and unsubscribers of the account: list, add, remove."
    }
  ],
  "paths": {
    "/token": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "getAccessToken",
        "summary": "Get access token",
        "security": [],
        "description": "Obtains a Bearer JWT token using client credentials.\nPass the returned `access_token` as `Authorization: Bearer <token>` in all subsequent requests.\n\n### Supported grant types\n\n| Grant type | Use case |\n|------------|----------|\n| `password` | Standard integration — username + API key |\n| `refresh_token` | Renew an expiring access token without re-authenticating |\n| `client_credentials` | Server-to-server with client ID only |\n| `zapier` / `automateio` / `microsoft-flow` | Third-party platform integrations |\n\n### Token lifetime\n- Access token: **30 days**\n- Refresh token: **90 days**\n\nUse `grant_type=refresh_token` to obtain a fresh access token without\nre-authenticating — your SDK should do this automatically when the\naccess token gets close to expiry (typical strategy: refresh when less\nthan 24 h remaining).\n\n### Storing tokens securely\nEven with a 30-day lifetime, anyone who obtains an `access_token` can\nimpersonate your account until it expires. Store tokens in a server-side\nsecret store (Vault, AWS Secrets Manager, Azure Key Vault, environment\nvariables on a hardened host). **Never** embed a token in:\n- client-side JavaScript or any browser bundle\n- mobile apps shipped to end users (the binary can be inspected)\n- public Git repositories or CI build logs\nIf a token is leaked, revoke it via your Mailpro account settings and\nrequest a new one.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "description": "Authentication grant type",
                    "enum": [
                      "password",
                      "refresh_token",
                      "client_credentials",
                      "zapier",
                      "automateio",
                      "microsoft-flow"
                    ],
                    "example": "password"
                  },
                  "username": {
                    "type": "string",
                    "description": "Your Mailpro username (email). Required for `password` grant.",
                    "example": "user@yourcompany.com"
                  },
                  "password": {
                    "type": "string",
                    "description": "Your Mailpro API key (GUID format). Required for `password` grant.",
                    "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  },
                  "refresh_token": {
                    "type": "string",
                    "description": "Previous refresh token. Required for `refresh_token` grant."
                  },
                  "client_id": {
                    "type": "string",
                    "description": "Your Mailpro client ID (integer). Required for `client_credentials` grant.",
                    "example": "12345"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token issued successfully",
            "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": "Invalid credentials or unsupported grant type",
            "content": {
              "application/json": {
                "examples": {
                  "invalid_grant": {
                    "summary": "Wrong credentials",
                    "value": {
                      "error": "invalid_grant",
                      "error_description": "The username or password is incorrect."
                    }
                  },
                  "no_api_access": {
                    "summary": "No API access",
                    "value": {
                      "error": "no_api_access",
                      "error_description": "The account doesn't have API access."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/Account/Me": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getCrmAccountMe",
        "summary": "Get account details",
        "description": "Returns the profile details and settings of the authenticated Mailpro account.",
        "responses": {
          "200": {
            "description": "Account details returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Account/Credits": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getCrmAccountCredits",
        "summary": "Get credit balance",
        "description": "Returns the remaining email and SMS credit balance for the account.",
        "responses": {
          "200": {
            "description": "Credit balance returned successfully",
            "content": {
              "application/json": {
                "example": {
                  "email_credits": 48500,
                  "sms_credits": 1200
                }
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Account/SubscriptionEndDate": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getCrmSubscriptionEndDate",
        "summary": "Get subscription end date",
        "description": "Returns the expiry date of the current Mailpro subscription.",
        "responses": {
          "200": {
            "description": "Subscription end date returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Account/Limits": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getCrmAccountLimits",
        "summary": "Get monthly API quota",
        "description": "Returns the monthly API request quota for the authenticated client.\n\n**Quota calculation:** the limit is calibrated from the email\nsubscription credits (typically `subscription_credits × 5`). For\na 10,000-email/month plan, this yields a 50,000-request monthly\nbudget. The exact mapping is set by the billing job at subscription\nsignup and reset on the **subscription anniversary date** (not the\n1st of the calendar month).\n\n**What consumes credits:**\n- Every authenticated API request to `v2`, `v3` or `sms` endpoints\n  (each call = 1 credit, regardless of the result — 2xx and most\n  4xx business errors both count).\n- Every webhook event dispatched to a subscribed endpoint\n  (e.g. `contact.created` fired to your subscribed webhook = 1 credit\n  on top of the API call that triggered it).\n\n**What is FREE (does not consume credits):**\n- `/health` endpoints (monitoring).\n- Requests that fail with `401` (auth fail), `403` (forbidden) or\n  `429` (rate-limited).\n- Webhook events for clients with no matching subscription\n  (cached short-circuit — no quota impact).\n",
        "security": [
          {
            "BearerAuth": []
          },
          {
            "LegacyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Quota snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "monthly_limit": {
                      "type": "integer",
                      "description": "Total credits allocated for the current billing month.",
                      "example": 50000
                    },
                    "remaining": {
                      "type": "integer",
                      "description": "Credits still available before the next reset.",
                      "example": 32847
                    },
                    "used": {
                      "type": "integer",
                      "description": "Credits consumed since the last reset (max(0, monthly_limit - remaining)).",
                      "example": 17153
                    },
                    "is_free": {
                      "type": "boolean",
                      "description": "True for free-tier accounts.",
                      "example": false
                    },
                    "remained_sends_for_free": {
                      "type": "integer",
                      "nullable": true,
                      "description": "Free-tier-specific counter. `null` for paid accounts.",
                      "example": null
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "No quota record found for this account (newly created account that has never made an API call; the row will be created on the first authenticated request)."
          }
        }
      }
    },
    "/Contact": {
      "post": {
        "tags": [
          "Contact"
        ],
        "operationId": "createContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Create a contact",
        "description": "Creates a new CRM contact with optional custom fields, list assignments, and tags.\n\n**Required:**\n- `email` — must be unique across the account\n- `lists` — at least one list assignment is mandatory\n\n**Phone numbers** accept `+41…`, `0041…` or `41…` with or without separators\n(max 12 digits on Contact — see **Phone number format** in the API description).\n\n**Custom fields** are identified by their `id` (from `GET /Field`).\nValues are validated against the field's type.\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": "Contact created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "400": {
            "description": "Invalid email format"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "406": {
            "description": "Business rule violation (see description for full list)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "email_exists": {
                    "summary": "Duplicate email",
                    "value": {
                      "Message": "Contact cannot be created. A contact already exist with the same Email"
                    }
                  },
                  "no_list": {
                    "summary": "No list provided",
                    "value": {
                      "Message": "Contact cannot be created. At least one List is necessary"
                    }
                  },
                  "unsubscribed_forever": {
                    "summary": "Permanently unsubscribed",
                    "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": "Import contacts (bulk)",
        "description": "Creates up to **1,000 contacts** in a single request for efficient bulk import.\nThe response includes a list of successfully created contacts and a list of errors\nfor contacts that could not be created (e.g. duplicate email, invalid data).\n\n**Note:** lists and tags used in bulk imports must **not** be linked to active\nautomation workflows. If they are, the entire import is rejected with 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": "Bulk import completed (check `errors` array for partial failures)",
            "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": "Invalid input"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "406": {
            "description": "Business rule violation (e.g. automation conflict, list limit reached)"
          }
        }
      }
    },
    "/Contact/{id}": {
      "get": {
        "tags": [
          "Contact"
        ],
        "operationId": "getContact",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Get a contact",
        "description": "Returns the details of a specific contact.\nUse `includeTags=true` to also return the tags assigned to the contact.\nUse `includeLists=true` to also return the lists the contact belongs to.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Contact UUID (from `POST /Contact` or `GET /List/{id}/Contact`)",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "includeTags",
            "in": "query",
            "description": "Set to `true` to include the contact's tag assignments in the response",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "includeLists",
            "in": "query",
            "description": "Set to `true` to include the contact's list memberships in the response",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contact returned successfully",
            "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": "Invalid or expired token"
          },
          "404": {
            "description": "Contact not found"
          }
        }
      },
      "put": {
        "tags": [
          "Contact"
        ],
        "operationId": "updateContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Update a contact",
        "description": "Updates a contact's email, phone number, custom fields, list assignments, and tags.\nThe request body replaces the contact's current data — include all lists and tags\nyou want the contact to remain assigned to.\n\n**Required in body:** `email` and at least one `lists` entry.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Contact UUID",
            "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": "Contact updated successfully",
            "content": {
              "application/json": {
                "example": {
                  "Result": "Successful operation"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Contact not found"
          },
          "422": {
            "description": "Validation error (email format, phone number, field value type mismatch)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Contact"
        ],
        "operationId": "deleteContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Delete a contact",
        "description": "Permanently deletes a contact from the CRM.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Contact UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          }
        ],
        "responses": {
          "200": {
            "description": "Contact deleted successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Contact not found"
          }
        }
      }
    },
    "/Contact/{id}/Activity": {
      "get": {
        "tags": [
          "Contact"
        ],
        "operationId": "getCrmContactActivity",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Activity of a contact over 90 days",
        "description": "Emails received, opened and clicked, favorite email client and latest campaigns of the contact.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Contact GUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Activity returned",
            "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": "Invalid or expired token"
          },
          "404": {
            "description": "The requested contact does not exist"
          }
        }
      }
    },
    "/Contact/{id}/Tag/{tagid}": {
      "put": {
        "tags": [
          "Contact"
        ],
        "operationId": "addTagToContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Add tag to contact",
        "description": "Assigns a tag to a contact. If the tag is already assigned, the operation is a no-op.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Contact UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "tagid",
            "in": "path",
            "required": true,
            "description": "Tag ID (from `GET /Tag`)",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5
          }
        ],
        "responses": {
          "200": {
            "description": "Tag assigned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Contact or tag not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Contact"
        ],
        "operationId": "removeTagFromContact",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Remove tag from contact",
        "description": "Removes a tag assignment from a contact.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Contact UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "tagid",
            "in": "path",
            "required": true,
            "description": "Tag ID",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5
          }
        ],
        "responses": {
          "200": {
            "description": "Tag removed successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Contact or tag not found"
          }
        }
      }
    },
    "/Contact/{id}/List/{listid}": {
      "put": {
        "tags": [
          "Contact"
        ],
        "operationId": "addContactToList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Add contact to list",
        "description": "Adds a contact to a specific list.\nReturns HTTP 404 if the contact was previously unsubscribed from this list.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Contact UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "listid",
            "in": "path",
            "required": true,
            "description": "List ID (from `GET /List`)",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "Contact added to list successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Contact, list not found, or contact was unsubscribed from this list"
          }
        }
      },
      "delete": {
        "tags": [
          "Contact"
        ],
        "operationId": "removeContactFromList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Remove contact from list",
        "description": "Removes a contact from a specific list. The contact itself is not deleted.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Contact UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "550e8400-e29b-41d4-a716-446655440000"
          },
          {
            "name": "listid",
            "in": "path",
            "required": true,
            "description": "List ID",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "Contact removed from list successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Contact or list not found"
          }
        }
      }
    },
    "/List": {
      "get": {
        "tags": [
          "List"
        ],
        "operationId": "getLists",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List contact lists",
        "description": "Returns all contact lists for the account.\nFilter by name or creation date. Sort alphabetically with `sortByName=true`.\nInclude the total contact count per list with `includeTotalContacts=true`.\n",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filter by list name (partial match)",
            "schema": {
              "type": "string"
            },
            "example": "newsletter"
          },
          {
            "name": "dateCreationFrom",
            "in": "query",
            "description": "Filter by creation date (from, ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "dateCreationTo",
            "in": "query",
            "description": "Filter by creation date (to, ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "description": "Items per page — 5,000 by default and at most",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "description": "1-based page number",
            "schema": {
              "type": "integer"
            },
            "example": 1
          },
          {
            "name": "sortByName",
            "in": "query",
            "description": "Set to `true` to sort alphabetically by name",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "includeTotalContacts",
            "in": "query",
            "description": "Set to `true` to include `total_contacts` count in each result",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Lists returned successfully",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": 101,
                    "name": "Newsletter subscribers",
                    "total_contacts": 4521,
                    "date_creation": "2025-01-15T10:30:00"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      },
      "post": {
        "tags": [
          "List"
        ],
        "operationId": "createList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Create a contact list",
        "description": "Creates a new contact list. Maximum 1,000 lists per account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListEntry"
              },
              "example": {
                "name": "Spring Campaign 2026"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "List created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/List"
                },
                "example": {
                  "id": 102,
                  "name": "Spring Campaign 2026",
                  "date_creation": "2026-04-14T12:00:00"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "406": {
            "description": "List limit (1,000) reached"
          }
        }
      }
    },
    "/List/{id}": {
      "get": {
        "tags": [
          "List"
        ],
        "operationId": "getList",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Get a contact list",
        "description": "Returns the details of a specific contact list.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "List ID",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "List returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "List not found"
          }
        }
      },
      "put": {
        "tags": [
          "List"
        ],
        "operationId": "updateList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Update a contact list",
        "description": "Renames an existing contact list.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "List ID",
            "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": "List updated successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "List not found"
          }
        }
      },
      "delete": {
        "tags": [
          "List"
        ],
        "operationId": "deleteList",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Delete a contact list",
        "description": "Permanently deletes a contact list.\nThe contacts themselves are not deleted — only their membership in this list is removed.\nLists used by active automation workflows cannot be deleted.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "List ID",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "List deleted successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "List not found"
          },
          "406": {
            "description": "List is used by an active automation workflow"
          }
        }
      }
    },
    "/List/{id}/Contact": {
      "get": {
        "tags": [
          "List"
        ],
        "operationId": "getListContacts",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List contacts in a list",
        "description": "Returns the contacts belonging to a specific list.\nFilter by email or date added. Include tags and list memberships with the corresponding flags.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "List ID",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          },
          {
            "name": "email",
            "in": "query",
            "description": "Filter by email (partial match)",
            "schema": {
              "type": "string"
            },
            "example": "@example.com"
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "description": "Items per page — 5,000 by default and at most",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          },
          {
            "name": "sortByEmail",
            "in": "query",
            "description": "Sort alphabetically by email",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "includeTags",
            "in": "query",
            "description": "Include tag assignments in results",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "includeLists",
            "in": "query",
            "description": "Include list memberships in results",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contacts returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "List not found"
          }
        }
      }
    },
    "/List/{listId}/Unsubscribers": {
      "get": {
        "tags": [
          "List"
        ],
        "operationId": "getListUnsubscribers",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List unsubscribers",
        "description": "Returns the contacts who have unsubscribed from a specific list.",
        "parameters": [
          {
            "name": "listId",
            "in": "path",
            "required": true,
            "description": "List ID",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 101
          }
        ],
        "responses": {
          "200": {
            "description": "Unsubscribers returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "List not found"
          }
        }
      }
    },
    "/Tag": {
      "get": {
        "tags": [
          "Tag"
        ],
        "operationId": "getTags",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List tags",
        "description": "Returns all tags for the account.",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filter by name (partial match)",
            "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": "Tags returned successfully",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": 5,
                    "name": "VIP customers"
                  },
                  {
                    "id": 6,
                    "name": "Newsletter openers"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      },
      "post": {
        "tags": [
          "Tag"
        ],
        "operationId": "createTag",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Create a tag",
        "description": "Creates a new tag. Maximum 1,000 tags per account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TagEntry"
              },
              "example": {
                "name": "VIP customers"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tag created successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "406": {
            "description": "Tag limit (1,000) reached"
          }
        }
      }
    },
    "/Tag/{id}": {
      "get": {
        "tags": [
          "Tag"
        ],
        "operationId": "getTag",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Get a tag",
        "description": "Returns a specific tag by its ID.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Tag ID",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5
          }
        ],
        "responses": {
          "200": {
            "description": "Tag returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Tag not found"
          }
        }
      },
      "put": {
        "tags": [
          "Tag"
        ],
        "operationId": "updateTag",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Update a tag",
        "description": "Renames an existing tag.",
        "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": "Tag updated successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Tag not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Tag"
        ],
        "operationId": "deleteTag",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Delete a tag",
        "description": "Permanently deletes a tag. The tag is removed from all contacts.\nTags used by active automation workflows cannot be deleted.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5
          }
        ],
        "responses": {
          "200": {
            "description": "Tag deleted successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Tag not found"
          },
          "406": {
            "description": "Tag is used by an active automation workflow"
          }
        }
      }
    },
    "/Tag/{id}/Contact": {
      "get": {
        "tags": [
          "Tag"
        ],
        "operationId": "getTagContacts",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List contacts with a tag",
        "description": "Returns the contacts that have been assigned a specific tag.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Tag ID",
            "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": "Contacts returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Tag not found"
          }
        }
      }
    },
    "/Field": {
      "get": {
        "tags": [
          "Field"
        ],
        "operationId": "listFields",
        "summary": "List custom fields",
        "description": "Returns all custom contact fields defined for the account.\nUse the returned `id` values when creating or updating contacts.\n",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filter by field name",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idFieldType",
            "in": "query",
            "description": "Filter by field type ID",
            "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": "Custom fields returned successfully",
            "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": "Invalid or expired token"
          }
        }
      },
      "post": {
        "tags": [
          "Field"
        ],
        "operationId": "createField",
        "summary": "Create a custom field",
        "description": "Creates a new custom contact field.\nMaximum 50 fields per account.\nUse the returned `id` when assigning values to contacts.\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": "Field created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Field"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "406": {
            "description": "Field limit (50) reached"
          }
        }
      }
    },
    "/Field/Types": {
      "get": {
        "tags": [
          "Field"
        ],
        "operationId": "listFieldTypes",
        "summary": "List field types",
        "description": "Returns the available field type identifiers to use when creating a custom field.\nCommon types: `1` = text, `2` = number, `3` = date.\n",
        "responses": {
          "200": {
            "description": "Field types returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Field/{id}": {
      "get": {
        "tags": [
          "Field"
        ],
        "operationId": "getField",
        "summary": "Get a custom field",
        "description": "Returns the definition and metadata of a specific custom field.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Field ID",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Field returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Field not found"
          }
        }
      },
      "put": {
        "tags": [
          "Field"
        ],
        "operationId": "updateField",
        "summary": "Update a custom field",
        "description": "Updates the name or personalisation key of a custom field.",
        "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": "Field updated successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Field not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Field"
        ],
        "operationId": "deleteField",
        "summary": "Delete a custom field",
        "description": "Permanently deletes a custom field and removes its value from all contacts.\nThis action cannot be undone.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Field deleted successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Field not found"
          }
        }
      }
    },
    "/Campaign": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "listCrmCampaigns",
        "summary": "List campaigns",
        "description": "Returns a paginated list of email campaigns with their performance metrics:\nsent, opens, clicks, unique clickers, bounces (`count_badmail`), unsubscribes,\ncomplaints, spam-trap hits and the deliverability score.\n",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 20
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Campaigns returned successfully",
            "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": "Invalid or expired token"
          }
        }
      }
    },
    "/Campaign/{id}": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaign",
        "summary": "Get a campaign",
        "description": "Returns the configuration and statistics summary of a specific campaign.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Campaign not found"
          }
        }
      },
      "put": {
        "tags": [
          "Campaign"
        ],
        "operationId": "updateCrmCampaign",
        "summary": "Update a campaign",
        "description": "Updates a scheduled campaign's title or send date.\nCampaigns can only be modified until **5 minutes before** the scheduled send time.\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": "Campaign updated successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Campaign not found"
          }
        }
      },
      "delete": {
        "tags": [
          "Campaign"
        ],
        "operationId": "deleteCrmCampaign",
        "summary": "Delete a campaign",
        "description": "Cancels and permanently deletes a campaign by its identifier.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign deleted successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Campaign not found"
          }
        }
      }
    },
    "/Campaign/Scheduled": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "listScheduledCrmCampaigns",
        "summary": "List my scheduled campaigns",
        "description": "Returns campaigns with `date_planned` in the future that have not yet been\ndispatched. Sorted by `date_planned` ascending.\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": "List of scheduled campaigns",
            "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": "Invalid or expired token"
          }
        }
      }
    },
    "/Campaign/{id}/Cancel": {
      "post": {
        "tags": [
          "Campaign"
        ],
        "operationId": "cancelScheduledCrmCampaign",
        "summary": "Cancel a scheduled campaign",
        "description": "Cancels a campaign scheduled in the future. Returns **409 Conflict** if the\ncampaign has already been dispatched or doesn't exist.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Campaign cancelled",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "cancelled": 5001
                }
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "409": {
            "description": "Already dispatched or not found"
          }
        }
      }
    },
    "/Campaign/{id}/Reschedule": {
      "patch": {
        "tags": [
          "Campaign"
        ],
        "operationId": "rescheduleCrmCampaign",
        "summary": "Reschedule a scheduled campaign",
        "description": "Updates the planned send date of a scheduled campaign. The new date must be\nin the future and within 90 days.\n\n**Body field name:** the API accepts `date_planned` (snake_case, primary)\nas well as the legacy aliases `PlanDate` and `scheduled_time`. Provide any\none of them — the first non-null is used.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Provide one of `date_planned`, `PlanDate`, or `scheduled_time`.",
                "properties": {
                  "date_planned": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Primary field name.",
                    "example": "2026-12-31T14:30:00"
                  },
                  "PlanDate": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Legacy alias accepted for backward compatibility."
                  },
                  "scheduled_time": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Legacy alias accepted for backward compatibility."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Campaign rescheduled",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "id": 5001,
                  "date_planned": "2026-12-31T14:30:00"
                }
              }
            }
          },
          "400": {
            "description": "Missing date — provide one of date_planned / PlanDate / scheduled_time"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "409": {
            "description": "Already dispatched",
            "not found": null,
            "or out of range": null
          }
        }
      }
    },
    "/Campaign/{id}/ByHour": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByHour",
        "summary": "Statistics by hour",
        "description": "Returns the hourly open and click breakdown for the campaign.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Hourly statistics returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Campaign/{id}/ByDay": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByDay",
        "summary": "Statistics by day",
        "description": "Returns the daily open and click breakdown for the campaign.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Daily statistics returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Campaign/{id}/Openings": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignOpenings",
        "summary": "List email opens",
        "description": "Returns a paginated list of contacts who opened the campaign email.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          },
          {
            "name": "email",
            "in": "query",
            "description": "Filter by recipient email",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Open list returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Campaign/{id}/Clicks": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignClicks",
        "summary": "List link clicks",
        "description": "Returns a paginated list of contacts who clicked a link in the campaign.",
        "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": "Click list returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Campaign/{id}/HardBounces": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignHardBounces",
        "summary": "List hard bounces",
        "description": "Returns the email addresses that hard-bounced during this campaign.",
        "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": "Hard bounce list returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Campaign/{id}/Links": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignLinks",
        "summary": "List tracked links",
        "description": "Returns click performance data for each tracked link in the campaign.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          },
          {
            "name": "link",
            "in": "query",
            "description": "Filter by link URL (partial match)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Link statistics returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Campaign/{id}/OS": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByOS",
        "summary": "Statistics by OS / device",
        "description": "Returns the open and click breakdown by operating system and device type.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "OS statistics returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Campaign/{id}/EmailClients": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByEmailClient",
        "summary": "Statistics by email client",
        "description": "Returns the open breakdown by email client (Gmail, Outlook, Apple Mail…).",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Email client statistics returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Campaign/{id}/Countries": {
      "get": {
        "tags": [
          "Campaign"
        ],
        "operationId": "getCrmCampaignStatsByCountry",
        "summary": "Statistics by country",
        "description": "Returns the geographic distribution of opens and clicks.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Country statistics returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Send/Mail": {
      "post": {
        "tags": [
          "Send"
        ],
        "operationId": "sendTransactionalEmailCrm",
        "summary": "Send transactional email",
        "description": "Sends a one-off transactional email to a single recipient.\n\n**Recipient:** provide `email` (inline address) **OR** `id_contact` (CRM contact) — not both.\nUsing `id_contact` enables automatic personalization with the contact's custom field values.\n\n**Content:** provide `body_html` (inline HTML) **OR** `id_message` (saved template) — not both.\nIf both are provided, `id_message` takes precedence.\n\n**Required:** `id_sender_email`, `subject`, and one of (`email` or `id_contact`),\nand one of (`body_html` or `id_message`).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendEmailRequest"
              },
              "example": {
                "id_sender_email": 201,
                "email": "alice@example.com",
                "subject": "Your order #12345 has shipped",
                "body_html": "<p>Hello {{first_name}}, your order is on its way!</p>",
                "reply_to": "support@yourcompany.com",
                "fields": [
                  {
                    "id": 1,
                    "value": "Alice"
                  }
                ],
                "attachments": [
                  {
                    "filename": "invoice.pdf",
                    "content": "JVBERi0xLjQKJVBERi0xLjQK..."
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Email sent or scheduled successfully",
            "content": {
              "application/json": {
                "example": {
                  "message": "Successful operation",
                  "id_single_send": 8001
                }
              }
            }
          },
          "400": {
            "description": "Invalid email format or missing required field"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Sender, message, or contact not found; or recipient is unsubscribed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "no_credits": {
                    "summary": "Insufficient credits",
                    "value": {
                      "Message": "Not enough credit available"
                    }
                  },
                  "unsubscribed": {
                    "summary": "Recipient unsubscribed",
                    "value": {
                      "Message": "Email address unsubscribed."
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation error (subject too long, body and message both empty, etc.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/Send/{id}/Status": {
      "get": {
        "tags": [
          "Send"
        ],
        "operationId": "getTransactionalStatusCrm",
        "summary": "Get delivery status of a transactional email",
        "description": "Returns the current delivery status of a transactional email sent with `POST /Send/Mail`. Pass the `id_single_send` that call returned.\n\nPossible values: `delivered` (accepted by the recipient server, see `diag_code`), `bounced` (rejected — `bounce_type: hard` invalid address or permanent refusal, `soft` refused after retries), `sent` (handed to the mail server, no answer yet), `scheduled` (not sent yet, `date_planned` in the future) and `queued` (waiting to be dispatched). `delivered` and `bounced` are final: stop polling. Every email has a final status at most 48 hours after sending; poll every 15 minutes at most (each call counts against your API quota) or use the `email.delivered` / `email.bounced` webhooks. Only the client that sent the email can read its status — any other id returns 404.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The `id_single_send` returned by `POST /Send/Mail`.",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 8001
          }
        ],
        "responses": {
          "200": {
            "description": "Current delivery status",
            "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": "Invalid or expired token"
          },
          "404": {
            "description": "No transactional send found with this id for this client"
          }
        }
      }
    },
    "/Send/Campaign": {
      "post": {
        "tags": [
          "Send"
        ],
        "operationId": "sendCampaignCrm",
        "summary": "Send email campaign",
        "description": "Sends a bulk email campaign to one or more contact lists.\n\n**Required:**\n- `id_message` — the template to send (from `GET /Message`)\n- `id_sender_email` — the sender address (from `GET /SenderEmail`)\n- `lists` — at least one target contact list\n- `campaign_title` — label for statistics tracking\n\n**Segment:** optionally filter recipients by a saved segment (`id_segment`).\nThe segment filters contacts within the specified lists.\n\n**Scheduling:** set `date_planned` to a future ISO 8601 date/time.\nCampaigns can be edited or cancelled until 5 minutes before the scheduled time.\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": "Campaign scheduled or sent successfully",
            "content": {
              "application/json": {
                "example": {
                  "Message": "Successful operation. Campaigns can be edited or deleted until 5 minutes prior to send.",
                  "IdCampaign": 8001
                }
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "402": {
            "description": "Insufficient credits",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Message, sender, list, or segment not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Campaign title empty or too long (max 150 chars)"
          }
        }
      }
    },
    "/Send": {
      "get": {
        "tags": [
          "Send"
        ],
        "operationId": "getSendWaiting",
        "summary": "List scheduled sends",
        "description": "Returns the list of email campaigns waiting to be sent (scheduled, not yet processed).",
        "parameters": [
          {
            "name": "subjectMessage",
            "in": "query",
            "description": "Filter by subject (partial match)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Waiting sends returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Send/{id}": {
      "delete": {
        "tags": [
          "Send"
        ],
        "operationId": "deleteSendWaiting",
        "summary": "Cancel a scheduled send",
        "description": "Cancels and deletes a scheduled email send that has not yet been processed.\nCannot cancel sends that are currently being processed.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Send ID (from `GET /Send`)",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 3001
          }
        ],
        "responses": {
          "200": {
            "description": "Scheduled send cancelled successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Scheduled send not found"
          }
        }
      }
    },
    "/Send/Mail/Scheduled": {
      "get": {
        "tags": [
          "Send"
        ],
        "operationId": "listScheduledCrmMails",
        "security": [
          {
            "OAuth2": [
              "email:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List scheduled single emails",
        "description": "Returns the single emails sent with `POST /Send/Mail` and a future date that have not left yet. Campaigns are listed by `GET /Campaign/Scheduled`.",
        "responses": {
          "200": {
            "description": "Scheduled emails returned",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id_single_send": 880123,
                    "email": "jane@example.com",
                    "subject": "Your invoice",
                    "date_planned": "2026-12-01T09:00:00"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Send/Mail/{id}/Cancel": {
      "post": {
        "tags": [
          "Send"
        ],
        "operationId": "cancelCrmMail",
        "security": [
          {
            "OAuth2": [
              "email:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Cancel a scheduled single email",
        "description": "Cancels the email as long as it has not left.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id_single_send` returned by `POST /Send/Mail`",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Email cancelled",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "cancelled": 880123
                }
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "409": {
            "description": "Send not found, or already dispatched"
          }
        }
      }
    },
    "/Send/Mail/{id}/Reschedule": {
      "patch": {
        "tags": [
          "Send"
        ],
        "operationId": "rescheduleCrmMail",
        "security": [
          {
            "OAuth2": [
              "email:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Reschedule a scheduled single email",
        "description": "Changes the send date. The new date must be in the future and within 90 days. `POST` is also accepted.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id_single_send` returned by `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": "New send date (ISO 8601)",
                    "example": "2026-12-15T09:00:00"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Email rescheduled",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "id": 880123,
                  "date_planned": "2026-12-15T09:00:00"
                }
              }
            }
          },
          "400": {
            "description": "Missing date"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "409": {
            "description": "Send not found, already dispatched, or date out of range"
          }
        }
      }
    },
    "/SenderEmail": {
      "get": {
        "tags": [
          "Sender Addresses"
        ],
        "operationId": "listCrmSenderEmails",
        "summary": "List sender addresses",
        "description": "Returns the validated sender email addresses configured for the account.",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Sender addresses returned successfully",
            "content": {
              "application/json": {
                "example": [
                  {
                    "id": 201,
                    "email": "newsletter@yourcompany.com",
                    "sender_name": "Your Company Newsletter",
                    "is_validated": true
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      },
      "post": {
        "tags": [
          "Sender Addresses"
        ],
        "operationId": "addCrmSenderEmail",
        "security": [
          {
            "OAuth2": [
              "email:templates:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Add a sender address",
        "description": "Adds the address with the same checks as the app and sends it a validation email. It can be used once the link in that email is clicked. An account can have up to 25 senders, validated or pending.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email",
                  "sender_name"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "Sender address",
                    "example": "news@example.com"
                  },
                  "sender_name": {
                    "type": "string",
                    "description": "Name shown to recipients",
                    "example": "Example News"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Address added. `domain_has_mx` is false when the domain has no MX record.",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "confirmation_sent": true,
                  "domain_has_mx": true
                }
              }
            }
          },
          "400": {
            "description": "`email` or `sender_name` missing"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "422": {
            "description": "Address refused: invalid, already among the senders of the account, public mail domain, forbidden name, or limit of 25 reached. The message gives the reason."
          },
          "503": {
            "description": "Temporarily unavailable, please try again"
          }
        }
      }
    },
    "/SenderEmail/{id}/ResendValidation": {
      "post": {
        "tags": [
          "Sender Addresses"
        ],
        "operationId": "resendCrmSenderValidation",
        "security": [
          {
            "OAuth2": [
              "email:templates:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Send the validation email again",
        "description": "For an address still waiting for validation.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id_sender_email` from `GET /SenderEmail`",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Email sent again",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "confirmation_sent": true
                }
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Sender not found"
          },
          "409": {
            "description": "This sender is already validated"
          },
          "503": {
            "description": "Temporarily unavailable, please try again"
          }
        }
      }
    },
    "/SenderEmail/{id}/Authentication": {
      "get": {
        "tags": [
          "Sender Addresses"
        ],
        "operationId": "getCrmSenderAuthentication",
        "security": [
          {
            "OAuth2": [
              "email:templates:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Check the domain of a sender (SPF, DKIM, DMARC, MX)",
        "description": "Checks the domain live in DNS and returns the records to publish. Read-only. DKIM is activated in the app: `dkim_settings_url` opens the right screen.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id_sender_email` from `GET /SenderEmail`",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Status of the domain. `spf.recommended_record` is built on your current record: publish it only when `spf.change_needed` is 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": "Invalid or expired token"
          },
          "404": {
            "description": "Sender not found"
          },
          "503": {
            "description": "Temporarily unavailable, please try again"
          }
        }
      }
    },
    "/History/Email": {
      "get": {
        "tags": [
          "History"
        ],
        "operationId": "getCrmHistoryEmail",
        "summary": "List email sending history",
        "description": "Returns the log of past email sends for the account.",
        "parameters": [
          {
            "name": "subjectMessage",
            "in": "query",
            "description": "Filter by subject (partial match)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "idCampaign",
            "in": "query",
            "description": "Filter by campaign ID",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "includeBodyHTML",
            "in": "query",
            "description": "Include the full HTML body in the response (slower)",
            "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": "Email history returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/History/SMS": {
      "get": {
        "tags": [
          "History"
        ],
        "operationId": "getCrmHistorySms",
        "summary": "List SMS sending history",
        "description": "Returns the log of past SMS sends for the account.",
        "parameters": [
          {
            "name": "dateSend",
            "in": "query",
            "description": "Filter from this date (ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "dateSendEnd",
            "in": "query",
            "description": "Filter up to this date (ISO 8601)",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "phoneNumber",
            "in": "query",
            "description": "Filter by recipient phone (partial)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "description": "SMS type filter",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "statusCode",
            "in": "query",
            "description": "DLR status code filter",
            "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": "SMS history returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Suppression/Bounces": {
      "get": {
        "tags": [
          "Suppression"
        ],
        "operationId": "listCrmBounces",
        "security": [
          {
            "OAuth2": [
              "email:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List the bounces of the account",
        "description": "Most recent first. The total is in the `X-Total-Count` header.",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "description": "`hard` or `soft`",
            "schema": {
              "type": "string",
              "enum": [
                "hard",
                "soft"
              ]
            }
          },
          {
            "name": "source",
            "in": "query",
            "description": "Where the bounce comes from: `list`, `contact`, `api` or `smtp`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "description": "Exact address",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dateFrom",
            "in": "query",
            "description": "From this date",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "dateTo",
            "in": "query",
            "description": "Up to this date",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bounces returned",
            "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` must be `hard` or `soft`"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Suppression/Bounces/{id}": {
      "delete": {
        "tags": [
          "Suppression"
        ],
        "operationId": "deleteCrmBounce",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Remove a bounce",
        "description": "`source` and `type` are required, as returned by `GET /Suppression/Bounces`: an `id` alone is not unique.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id` of the bounce",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": true,
            "description": "`list`, `contact`, `api` or `smtp`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": true,
            "description": "`hard` or `soft`",
            "schema": {
              "type": "string",
              "enum": [
                "hard",
                "soft"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Bounce removed",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "id": 5521,
                  "restored": false
                }
              }
            }
          },
          "400": {
            "description": "`source` or `type` missing"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Bounce not found"
          }
        }
      }
    },
    "/Suppression/Bounces/{id}/Restore": {
      "post": {
        "tags": [
          "Suppression"
        ],
        "operationId": "restoreCrmBounce",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Put a contact with a hard bounce back in its lists",
        "description": "Only for a hard bounce with the source `contact`.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id` of the bounce",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contact put back",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "id": 5521,
                  "restored": true
                }
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Hard bounce of a contact not found, or address no longer active"
          }
        }
      }
    },
    "/Suppression/Unsubscribers": {
      "get": {
        "tags": [
          "Suppression"
        ],
        "operationId": "listCrmUnsubscribers",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List the unsubscribers of the account",
        "description": "Most recent first. The total is in the `X-Total-Count` header.",
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "description": "Where the unsubscribe comes from: `contact`, `list`, `smtp`, `api` or `api3`",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "description": "Exact address",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "dateFrom",
            "in": "query",
            "description": "From this date",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "dateTo",
            "in": "query",
            "description": "Up to this date",
            "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": "Unsubscribers returned",
            "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": "Invalid or expired token"
          }
        }
      },
      "post": {
        "tags": [
          "Suppression"
        ],
        "operationId": "addCrmUnsubscriber",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Add an unsubscriber",
        "description": "Without `id_list`, the address is unsubscribed for the whole account. With it, from that unsubscribe list only.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "description": "Address to unsubscribe",
                    "example": "stop@example.com"
                  },
                  "comment": {
                    "type": "string",
                    "description": "Free comment",
                    "example": "Asked by phone"
                  },
                  "id_list": {
                    "type": "integer",
                    "format": "int64",
                    "description": "Unsubscribe list of the account (optional)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done. `already_unsubscribed` is true when the address already was.",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "already_unsubscribed": false
                }
              }
            }
          },
          "400": {
            "description": "A valid email is required"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "`id_list` is not an unsubscribe list of this account"
          }
        }
      }
    },
    "/Suppression/Unsubscribers/{id}": {
      "delete": {
        "tags": [
          "Suppression"
        ],
        "operationId": "deleteCrmUnsubscriber",
        "security": [
          {
            "OAuth2": [
              "contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Remove an unsubscriber",
        "description": "`source` is required, as returned by `GET /Suppression/Unsubscribers`: an `id` alone is not unique.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "`id` of the unsubscriber",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": true,
            "description": "`contact`, `list`, `smtp`, `api` or `api3`",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Unsubscriber removed",
            "content": {
              "application/json": {
                "example": {
                  "result": "ok",
                  "removed": 9012
                }
              }
            }
          },
          "400": {
            "description": "`source` is required"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Unsubscriber not found"
          }
        }
      }
    },
    "/Segment": {
      "get": {
        "tags": [
          "Segment"
        ],
        "operationId": "getSegments",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List segments",
        "description": "Returns all saved dynamic segments for the account.",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Segments returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Segment/{id}/Contact": {
      "get": {
        "tags": [
          "Segment"
        ],
        "operationId": "getCrmSegmentContacts",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List the contacts of a segment",
        "description": "With `id_list`, returns only the contacts of that list in the segment: those a campaign list + segment would reach. The total is in the `X-Total-Count` header.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Segment ID",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "id_list",
            "in": "query",
            "description": "Limit to this list (optional)",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contacts returned"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Segment or list not found"
          }
        }
      }
    },
    "/Segment/{id}/Count": {
      "get": {
        "tags": [
          "Segment"
        ],
        "operationId": "countCrmSegmentContacts",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Count the contacts of a segment",
        "description": "The number only, to know the size of a send beforehand. With `id_list`, counts only the contacts of that list in the segment.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Segment ID",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          },
          {
            "name": "id_list",
            "in": "query",
            "description": "Limit to this list (optional)",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Number of contacts",
            "content": {
              "application/json": {
                "example": {
                  "total": 1250
                }
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Segment or list not found"
          }
        }
      }
    },
    "/Segment/{id}/contact/{contactId}/check": {
      "get": {
        "tags": [
          "Segment"
        ],
        "operationId": "checkContactInSegment",
        "security": [
          {
            "OAuth2": [
              "contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Check if a contact matches a segment",
        "description": "Verifies whether a specific contact (by GUID) currently matches the rules\nof a given segment. Useful for triggering automation logic on demand.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Segment ID",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 4001
          },
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "description": "Contact GUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "11111111-2222-3333-4444-555555555555"
          }
        ],
        "responses": {
          "200": {
            "description": "Returns match result"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Segment or contact not found"
          }
        }
      }
    },
    "/FolderList": {
      "get": {
        "tags": [
          "Folders"
        ],
        "operationId": "getFolderLists",
        "summary": "List list folders",
        "description": "Returns the folders used to organize contact lists.",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filter by folder name (partial)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "List folders returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/FolderList/{id}": {
      "get": {
        "tags": [
          "Folders"
        ],
        "operationId": "getFolderListById",
        "summary": "Get a single list folder",
        "description": "Returns the details of one list folder by its ID.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Folder ID",
            "schema": {
              "type": "integer"
            },
            "example": 12
          }
        ],
        "responses": {
          "200": {
            "description": "Folder returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "Folder not found"
          }
        }
      }
    },
    "/Sms/SingleSMS": {
      "post": {
        "tags": [
          "SMS"
        ],
        "operationId": "sendSingleSmsCrm",
        "summary": "Send single SMS",
        "description": "Sends a single SMS to one phone number directly.\nUse this for transactional messages (OTPs, order confirmations, alerts).\nThe phone number accepts `+41…`, `0041…` or `41…` with or without separators —\nsee **Phone number format** in the API description for accepted / rejected inputs.\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 sent or scheduled successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "422": {
            "description": "Invalid phone number format"
          }
        }
      }
    },
    "/Sms/Campaign": {
      "post": {
        "tags": [
          "SMS"
        ],
        "operationId": "sendSmsCampaignCrm",
        "summary": "Send SMS campaign",
        "description": "Sends an SMS message to the `phone_number` field of all contacts in one or more lists.\nContacts without a phone number are skipped.\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": "SMS campaign sent or scheduled successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Sms": {
      "get": {
        "tags": [
          "SMS"
        ],
        "operationId": "getSmsSends",
        "summary": "List SMS history",
        "description": "Returns the log of SMS sends (both single and campaign) for the account.",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "SMS history returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Sms/{id}": {
      "delete": {
        "tags": [
          "SMS"
        ],
        "operationId": "deleteSmsSendCrm",
        "summary": "Cancel a scheduled SMS",
        "description": "Cancels a pending scheduled SMS send.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "SMS send ID",
            "schema": {
              "type": "integer",
              "format": "int64"
            },
            "example": 5001
          }
        ],
        "responses": {
          "200": {
            "description": "Scheduled SMS cancelled successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "404": {
            "description": "SMS send not found"
          }
        }
      }
    },
    "/Sms/Scheduled": {
      "get": {
        "tags": [
          "SMS"
        ],
        "operationId": "listScheduledSmsCrm",
        "summary": "List scheduled SMS sends",
        "description": "Returns SMS sends with a future `date_planned` (not yet dispatched), ordered by date ascending.",
        "parameters": [
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 100
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Scheduled SMS sends returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Sms/{id}/Cancel": {
      "post": {
        "tags": [
          "SMS"
        ],
        "operationId": "cancelScheduledSmsCrm",
        "summary": "Cancel a scheduled SMS",
        "description": "Cancels a scheduled SMS send only if it has not yet been dispatched.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "SMS send ID",
            "schema": {
              "type": "integer"
            },
            "example": 5001
          }
        ],
        "responses": {
          "200": {
            "description": "Scheduled SMS cancelled successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "409": {
            "description": "SMS send not found, not owned by client, or already dispatched"
          }
        }
      }
    },
    "/Sms/{id}/Reschedule": {
      "patch": {
        "tags": [
          "SMS"
        ],
        "operationId": "rescheduleScheduledSmsCrm",
        "summary": "Reschedule a scheduled SMS",
        "description": "Updates the planned send date of a scheduled SMS. The new date must be\nin the future and within 90 days. Fails if the SMS has already been dispatched.\n\n**Body field name:** the API accepts `date_planned` (snake_case, primary)\nas well as the legacy aliases `PlanDate` and `scheduled_time`. Provide any\none of them.\n",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "SMS send ID",
            "schema": {
              "type": "integer"
            },
            "example": 5001
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Provide one of `date_planned`, `PlanDate`, or `scheduled_time`.",
                "properties": {
                  "date_planned": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Primary field name.",
                    "example": "2026-05-10T14:00:00Z"
                  },
                  "PlanDate": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Legacy alias accepted for backward compatibility."
                  },
                  "scheduled_time": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Legacy alias accepted for backward compatibility."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Scheduled SMS rescheduled successfully"
          },
          "400": {
            "description": "Missing date — provide one of date_planned / PlanDate / scheduled_time"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "409": {
            "description": "Reschedule failed (not found, already dispatched, or date out of range)"
          }
        }
      }
    },
    "/FolderMessage": {
      "get": {
        "tags": [
          "Folders"
        ],
        "operationId": "getFolderMessages",
        "summary": "List message folders",
        "description": "Returns the folders used to organize email message templates.",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "description": "Filter by folder name",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "numberOfRecords",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 50
          },
          {
            "name": "pageNumber",
            "in": "query",
            "schema": {
              "type": "integer"
            },
            "example": 1
          }
        ],
        "responses": {
          "200": {
            "description": "Message folders returned successfully"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Import/Upload": {
      "post": {
        "tags": [
          "Import"
        ],
        "operationId": "uploadImportFile",
        "summary": "Upload a contacts file (CSV / XLSX) for bulk import",
        "description": "Upload a CSV or XLSX file to import contacts into a list.\nThe file is stored and queued for asynchronous processing — the endpoint\nreturns an `import_job_id` immediately.\n\n**Request type:** `multipart/form-data`\n\n**Required form fields:**\n- `id_list` (integer) — target list ID\n- `file` (binary) — the CSV or XLSX file to upload\n- `json_match_fields` (string, JSON) — which column of the file goes to\n  which contact field. `email_position` = 0-based column holding the\n  email (required), `phone_position` = column holding the mobile\n  number or `-1`, `fields` = ids of the other fields (see\n  `GET /Field`), `fields_position` = column of each of them,\n  `update_fields` = update existing contacts. A missing or invalid mapping returns `400` and no\n  import job is created.\n\n**Optional:**\n- `webhook_url` (string) — HTTPS URL called when processing finishes.\n  Receives a JSON payload with the final status (success / failed) and\n  counts of imported / skipped / invalid rows.\n\n**File format:**\n- UTF-8 encoded\n- First row may hold headers (detected automatically: a first row\n  without any email address is skipped)\n- Columns are mapped by `json_match_fields`\n- Max file size: 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 of the destination list",
                    "example": 11088
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "CSV or XLSX file (UTF-8, max 50 MB)"
                  },
                  "json_match_fields": {
                    "type": "string",
                    "description": "JSON mapping of the file columns to contact fields",
                    "example": "{\"email_position\":0,\"phone_position\":-1,\"fields\":[1315597,1315598],\"fields_position\":[1,2],\"update_fields\":true}"
                  },
                  "webhook_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Callback URL invoked when processing finishes",
                    "example": "https://example.com/hooks/mailpro-import"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "File accepted and queued",
            "content": {
              "application/json": {
                "example": {
                  "result": "OK",
                  "import_job_id": 14,
                  "internal_name": "230575-20260420142615-contacts.csv"
                }
              }
            }
          },
          "400": {
            "description": "No file uploaded, missing id_list, or missing / invalid json_match_fields"
          },
          "401": {
            "description": "Invalid or expired token"
          },
          "500": {
            "description": "Server or storage error"
          }
        }
      }
    },
    "/Webhook": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Webhook management service (separate from the main API host)"
        }
      ],
      "get": {
        "summary": "List webhooks",
        "description": "Returns every webhook configured on the authenticated account.",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "Array of webhooks",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Webhook"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      },
      "post": {
        "summary": "Create a webhook",
        "description": "Creates a new webhook subscription. The response contains a `secret` that\nis **shown only once** — store it immediately, server-side. Use it to\nverify the HMAC signature of every incoming payload.\n\n**Limits**: max 20 webhooks per account, max 50 events per 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": "Slack integration"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook created — `secret` is returned only here",
            "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": "Slack integration",
                  "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": "Invalid URL, events list, or limit exceeded"
          },
          "401": {
            "description": "Invalid or expired token"
          }
        }
      }
    },
    "/Webhook/{id}": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Webhook management service"
        }
      ],
      "parameters": [
        {
          "in": "path",
          "name": "id",
          "required": true,
          "schema": {
            "type": "integer",
            "format": "int64"
          }
        }
      ],
      "get": {
        "summary": "Get a webhook",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "The webhook",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      },
      "put": {
        "summary": "Update a webhook",
        "description": "Partial update — only the sent fields are modified.",
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated webhook",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Webhook"
                }
              }
            }
          },
          "400": {
            "description": "Invalid payload"
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      },
      "delete": {
        "summary": "Delete a webhook",
        "description": "Permanently deletes the webhook and its full delivery history.",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "204": {
            "description": "Deleted"
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      }
    },
    "/Webhook/{id}/test": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Webhook management service"
        }
      ],
      "parameters": [
        {
          "in": "path",
          "name": "id",
          "required": true,
          "schema": {
            "type": "integer",
            "format": "int64"
          }
        }
      ],
      "post": {
        "summary": "Send a test event",
        "description": "Queues an immediate `ping` event (or any other event type via the\n`eventType` body field) to your endpoint. Useful to verify reachability\nand signature verification before relying on real events.\n",
        "tags": [
          "Webhooks"
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "eventType": {
                    "type": "string",
                    "default": "ping",
                    "description": "Override the event type (must be subscribed by this webhook). `ping` always works."
                  }
                }
              },
              "example": {
                "eventType": "ping"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Event queued for dispatch",
            "content": {
              "application/json": {
                "example": {
                  "deliveryId": 1234,
                  "eventId": "evt_8f3a9b2c4d1e5f6789abcdef01234567",
                  "eventType": "ping"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      }
    },
    "/Webhook/{id}/rotate-secret": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Webhook management service"
        }
      ],
      "parameters": [
        {
          "in": "path",
          "name": "id",
          "required": true,
          "schema": {
            "type": "integer",
            "format": "int64"
          }
        }
      ],
      "post": {
        "summary": "Rotate the HMAC secret",
        "description": "Generates a new HMAC secret and invalidates the old one immediately.\nThe new secret is returned **once** in the response — store it. Every\ndelivery from this point on will be signed with the new secret.\n\nUse this if you suspect your secret has been compromised.\n",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "New secret (visible once)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "secret": {
                      "type": "string"
                    }
                  }
                },
                "example": {
                  "secret": "whsec_XyzA9876BcdE5432FghI1098JklM7654NopQ"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      }
    },
    "/Webhook/{id}/deliveries": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Webhook management service"
        }
      ],
      "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": "Pagination cursor (= last delivery ID seen). Returns deliveries with `id < cursor`."
        }
      ],
      "get": {
        "summary": "List delivery attempts",
        "description": "Returns the history of dispatch attempts for this webhook (30-day retention).\nUseful for debugging — which events were sent, status codes received,\nretry counts, error messages.\n",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "200": {
            "description": "Array of delivery attempts (newest first)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WebhookDelivery"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found"
          }
        }
      }
    },
    "/Webhook/{id}/deliveries/{deliveryId}/replay": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/v3",
          "description": "Webhook management service"
        }
      ],
      "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": "Replay a delivery",
        "description": "Queues a fresh delivery attempt with the same payload as the original.\nUseful to re-trigger a failed delivery after fixing your endpoint.\nA NEW `Webhook_Delivery` row is created (the original is preserved\nfor audit). The replay goes through the standard retry policy\n(10s → 1min → 5min → 30min → 2h → 6h → 24h, then abandoned).\n",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "202": {
            "description": "Replay queued",
            "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 or delivery not found"
          }
        }
      }
    }
  }
}