{
  "openapi": "3.0.3",
  "info": {
    "title": "Mailpro™ SMS API",
    "version": "2",
    "description": "The Mailpro™ SMS API lets you programmatically manage phone contact lists,\nphone numbers, and send bulk or individual SMS messages.\n\n## Authentication\n\nEvery endpoint accepts **three authentication methods**. Use whichever fits\nyour stack.\n\n- **OAuth 2.0** — scoped, revocable tokens for third-party apps and\n  integration platforms (Zapier, Make, n8n). **OAuth tokens are scope-gated\n  per endpoint** (see below).\n- **Bearer JWT** (legacy) — an HMAC token from `POST /token`, not scoped.\n- **id_client + api_key** (legacy) — client ID + API key.\n\n> Legacy Bearer JWT and id_client+api_key keep working unchanged and are\n> **not** scope-gated. Only OAuth 2.0 access tokens are subject to the\n> per-endpoint scope checks shown on each operation below.\n\n### Method 1 — OAuth 2.0 (recommended for integrations)\n\nMailpro runs **one** OAuth 2.0 authorization server for all of its APIs, at a\nsingle version-neutral surface: **`https://api.mailpro.com/oauth/`**. There is\nno per-version OAuth endpoint — you obtain a token from `/oauth/token` and the\n**same** token works on this SMS API, on CRM v3 and on Email v2. Which\nendpoints it may call is governed by the token's **scopes**, not the API base\nURL. Issuer (`iss` claim): `https://api.mailpro.com/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 | `GET https://api.mailpro.com/oauth/.well-known/jwks` |\n| Discovery | `GET https://api.mailpro.com/oauth/.well-known/openid-configuration` |\n\nGrants: `authorization_code` **+ PKCE (S256, mandatory)**, `client_credentials`,\n`refresh_token` (rotation). Access-token TTL **20 min**; refresh token **30 days**\n(sliding, single-use); auth code **60 s**.\n\n**Quickstart — get a token and call this API** (replace placeholders; never\ncommit a real secret):\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=sms:send sms:read\"\n```\n\nThen call this API with `Authorization: Bearer ACCESS_TOKEN`. The same token\nworks on the CRM v3 and Email v2 APIs. A token missing an endpoint's required\nscope gets **403 `insufficient_scope`**. Full flow walk-through and scope\ncatalog: see the **CRM v3** API docs › Authentication. SMS-relevant scopes:\n`sms:send`, `sms:read`, `sms:contacts:read`, `sms:contacts:write`,\n`account:read`.\n\n### Method 2 — Bearer JWT (legacy)\n\n```\nAuthorization: Bearer <access_token>\n```\n\nGet a token via `POST /token` (form-encoded, OAuth2 password grant):\n\n```\nPOST https://api.mailpro.com/sms/token\nContent-Type: application/x-www-form-urlencoded\n\ngrant_type=password&username=YOUR_USERNAME&password=YOUR_API_KEY\n```\n\nThe same token is also valid on the CRM API v3 and the Email v2 API —\nauthenticate once, call any API. Tokens are valid for **30 days** by default;\nuse `grant_type=refresh_token` to renew without re-authenticating. Not scoped\n— prefer OAuth 2.0 for installable integrations.\n\n### Method 3 — id_client + api_key\n\nTwo credentials:\n\n| Parameter   | Type    | Description |\n|-------------|---------|-------------|\n| `id_client` | integer | Your numeric Mailpro client ID, found in **Settings → API access** |\n| `api_key`   | string  | Your API key (GUID format), found in **Settings → API access** |\n\nSend them in either of these places — pick one:\n\n1. **HTTP headers (recommended)** — `id_client: 12345` and `api_key: a1b2c3d4-…`.\n   URLs are logged by web servers, CDNs and proxies, so a key in the query string\n   can leak into access logs. Headers are not logged.\n2. **Query string (legacy, still works)** — `?id_client=12345&api_key=…`.\n   Kept for backward compatibility with existing integrations. Switch to\n   headers as soon as you can.\n3. **JSON / form body (POST only)** — same field names inside the request body.\n\nIf you provide credentials in more than one place, the **header wins**.\n\n### OAuth scope enforcement per endpoint (SMS)\n\nFor OAuth-issued tokens, each endpoint group requires a scope (legacy auth is\nexempt). Each operation lists its scope under **Authorization**. Summary:\n\n| Endpoints | Required scope |\n|-----------|----------------|\n| `/send/*` (bulk, single, scheduling) | `sms:send` |\n| `/history/list`, `/credit/get` | `sms:read` |\n| `/phone/list`, `/list/list` | `sms:contacts:read` |\n| `/phone/{add,edit,del}`, `/list/{edit,del}`, `/import/upload` | `sms:contacts:write` |\n| `/account/limits` | `account:read` |\n\n> `POST /list/add` (create a list) is currently reachable by any\n> valid token — it is not yet scope-gated. Scope rejection on the gated\n> endpoints returns **403** with\n> `{ \"error\": \"insufficient_scope\", \"required_scope\": \"…\" }`.\n\n## Response format\n\nAll endpoints support `json` (default) and `xml`. Specify the format in the URL:\n- `/credit/get.json`\n- `/credit/get.xml`\n\nField names in responses are **PascalCase** (`TotalCredits`, `FirstName`, …),\nwhile request parameters are **snake_case** (`id_client`, `address_book_id`, …).\nList endpoints wrap their rows in an `Items` array.\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\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## SMS credit cost\n\nOne SMS costs **country credits × number of parts**.\n\n**Country credits** — each destination country has a price of 1, 2 or 3 credits,\nbased on the phone's international prefix. Sending to a number whose prefix we\ndon't price is rejected with `400`.\n\n**Number of parts** — a message longer than one SMS is split by the operator and\neach part is charged. The limit depends on the characters used:\n\n| Alphabet | Single message | Per part when split |\n|----------|----------------|---------------------|\n| GSM-7 (standard latin) | 160 chars | 153 chars |\n| Unicode (accented beyond GSM-7, Cyrillic, emoji, …) | 70 chars | 67 chars |\n\nA **single** character outside GSM-7 switches the **whole** message to Unicode,\nso one emoji can take a 160-character message from 1 part to 3. The characters\n`^ { } [ ] ~ | \\` and `€` count as **two** each, even though they are GSM-7.\n\n*Example:* a 300-character message in standard latin = 2 parts; sent to a\n2-credit country, it costs 2 × 2 = **4 credits** per recipient.\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## Error codes\n\n| HTTP | Code | Meaning |\n|------|------|---------|\n| 400  | 103  | Malformed input — missing or invalid required parameter |\n| 202  | 202  | Insufficient SMS credits |\n| 401  |      | Invalid `id_client` / `api_key` |\n| 500  | 500  | Internal error |\n\n## Webhooks\n\nMailpro™ can notify your server in real time when SMS events happen on your\naccount (an SMS is delivered, an SMS fails, your credit balance is low, 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 /webhooks`. 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\":       \"sms.queued\",\n  \"created\":    1748194800,\n  \"apiVersion\": \"sms\",\n  \"account\":    { \"id\": 12345, \"name\": \"ACME Corp\" },\n  \"data\":       {\n    \"messageId\":   12345,\n    \"to\":          \"41791234567\",\n    \"queuedAt\":    \"2026-05-27T14:32:12.0123456Z\",\n    \"scheduledAt\": \"2026-05-27T14:32:12.0123456Z\",\n    \"parts\":       1\n  }\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. |\n| `created` | Unix seconds UTC | Included in the HMAC signature. |\n| `apiVersion` | `\"sms\"` for events emitted by `POST /sms/send/add_single`; `\"v3\"` for events emitted by `POST /v3/Sms/SingleSMS`; `\"watcher\"` for DLR-driven events emitted by the worker. | Which Mailpro component published the event. |\n| `account.id` | int | Your Mailpro client ID (= `id_client`). |\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\nOnly the events listed below are actually emitted. The shapes are the literal\nJSON sent by the platform — match these field names exactly when parsing.\n\n| Event | Trigger | `data` shape |\n|-------|---------|--------------|\n| `ping` | Test via `POST /webhooks/{id}/test` | `{ message }` |\n| `sms.queued` | `POST /sms/send/add_single` or `POST /v3/Sms/SingleSMS` accepted into the Mailpro send queue | `{ messageId: int, to: string, queuedAt: ISO-8601, scheduledAt: ISO-8601, parts: int }` |\n| `sms.delivered` | DLR with carrier code `004` or `008` (success codes) | `{ messageId: int, phone: string, stateCode: string, dlrDate: datetime }` |\n| `sms.failed` | DLR with any other carrier code (timeout, undelivered, rejected, blacklisted, …) | `{ messageId: int, phone: string, stateCode: string, dlrDate: datetime }` |\n| `credits.sms.low` | SMS credit balance fell under your alert threshold (worker `CreditsLowWatcher`) | `{ remaining: int, threshold: int, alertedAt: ISO-8601 }` |\n\n> **Note** — there is no `sms.sent` event. The carrier DLR is the only feedback\n> the platform receives between \"queued\" and \"delivered/failed\", so the next\n> event you receive after `sms.queued` is either `sms.delivered` (success) or\n> `sms.failed` (error). Most messages reach the carrier within seconds and a\n> DLR within minutes; mobile-network conditions can occasionally extend this\n> to hours.\n\n> **Field naming** — `sms.queued` uses `to` (the destination as submitted by\n> the API caller, after normalization). `sms.delivered` / `sms.failed` use\n> `phone` (the destination as returned by the carrier on the DLR). They will\n> be the same digits in practice, but the names differ because the events\n> originate from different code paths.\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: sms.delivered\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 /webhooks/{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/sms",
      "description": "Production"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "IdClientHeader": [],
      "ApiKeyHeader": []
    },
    {
      "IdClientQuery": [],
      "ApiKeyQuery": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Bearer JWT token obtained from `POST /sms/token` (form-encoded, password\ngrant). The same token is also valid on the CRM API v3 and the\nEmail v2 API — authenticate once, call any API.\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": "id_client",
        "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": "api_key",
        "description": "Your API key (GUID) — **preferred** transport (HTTP header).\n"
      },
      "IdClientQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "id_client",
        "description": "Your numeric Mailpro client ID — **legacy**, kept for backward compatibility.\nSwitch to the `id_client` header when you can.\n"
      },
      "ApiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "api_key",
        "description": "Your API key (GUID) — **legacy**, kept for backward compatibility.\n"
      }
    },
    "parameters": {
      "format": {
        "name": "format",
        "in": "path",
        "required": true,
        "description": "Response format",
        "schema": {
          "type": "string",
          "enum": [
            "json",
            "xml"
          ],
          "default": "json"
        },
        "example": "json"
      },
      "numberOfRecords": {
        "name": "NumberOfRecords",
        "in": "header",
        "required": false,
        "description": "Items per page — 5,000 by default and at most (see Pagination). HTTP header or query parameter.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 5000,
          "default": 5000
        },
        "example": 100
      },
      "pageNumber": {
        "name": "PageNumber",
        "in": "header",
        "required": false,
        "description": "1-based page number for paginated responses. Sent as HTTP header.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        },
        "example": 1
      },
      "sortByName": {
        "name": "SortByName",
        "in": "header",
        "required": false,
        "description": "Set to `true` to sort results alphabetically by name.",
        "schema": {
          "type": "boolean",
          "default": false
        }
      }
    },
    "schemas": {
      "TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string",
            "description": "JWT access token — use as `Authorization: Bearer <token>` in subsequent requests."
          },
          "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
          }
        }
      },
      "PhoneList": {
        "type": "object",
        "properties": {
          "AddressBookId": {
            "type": "integer",
            "description": "Unique identifier of the phone list",
            "example": 42
          },
          "Name": {
            "type": "string",
            "description": "Display name of the phone list",
            "example": "Newsletter subscribers"
          },
          "TotalNumberPhones": {
            "type": "integer",
            "description": "Number of phone numbers in this list",
            "example": 1547
          }
        }
      },
      "PhoneNumber": {
        "type": "object",
        "properties": {
          "Id": {
            "type": "integer",
            "description": "Unique identifier of the phone number entry",
            "example": 1001
          },
          "Number": {
            "type": "string",
            "description": "Phone number in bare international format (stored form — see \"Phone number format\" in the API description for accepted input formats)",
            "example": "41791234567"
          },
          "FirstName": {
            "type": "string",
            "description": "First name of the contact",
            "example": "Alice"
          },
          "LastName": {
            "type": "string",
            "description": "Last name of the contact",
            "example": "Dupont"
          }
        }
      },
      "SingleSend": {
        "type": "object",
        "properties": {
          "Id": {
            "type": "integer",
            "description": "Unique identifier of the created send",
            "example": 5001
          },
          "Message": {
            "type": "string",
            "description": "The SMS message text",
            "example": "Your verification code is 8472"
          },
          "ScheduledTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the SMS will be (or was) sent",
            "example": "2026-04-15T09:00:00"
          },
          "To": {
            "type": "string",
            "description": "Recipient phone number",
            "example": "0041791234567"
          },
          "Charge": {
            "type": "string",
            "description": "Credits actually billed for this send = **country credits × number of parts**.\nE.g. a 1-part SMS to Panama (+507, 3 credits/SMS) → `\"3\"`; a 2-part SMS → `\"6\"`.\n",
            "example": "3"
          },
          "Parts": {
            "type": "integer",
            "description": "Number of SMS segments (GSM 03.38 parts) the message was split into.",
            "example": 1
          }
        }
      },
      "BulkSend": {
        "type": "object",
        "properties": {
          "Id": {
            "type": "integer",
            "description": "Unique identifier of the created bulk send",
            "example": 6001
          },
          "Message": {
            "type": "string",
            "description": "The SMS message text",
            "example": "Spring sale — 20% off all products this weekend!"
          },
          "ScheduledTime": {
            "type": "string",
            "format": "date-time",
            "description": "When the SMS campaign will be (or was) sent",
            "example": "2026-04-20T10:00:00"
          },
          "To": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "List of list IDs targeted by this send",
            "example": [
              "42"
            ]
          },
          "Charge": {
            "type": "string",
            "description": "Total credits billed for the whole send = **Σ (country credits × parts)**\nover every phone in the list.\n",
            "example": "150"
          },
          "Parts": {
            "type": "integer",
            "description": "Number of SMS segments (GSM 03.38 parts) per message.",
            "example": 1
          }
        }
      },
      "CreditBalance": {
        "type": "object",
        "properties": {
          "TotalCredits": {
            "type": "number",
            "description": "Remaining SMS credit balance (plan credits + monthly credits).\n\nThis is a credit count, not a number of messages: one SMS costs\n`country credits x number of parts` — see \"SMS credit cost\" in the\nAPI description.\n",
            "example": 5000
          }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "Error": {
            "type": "string",
            "description": "Human-readable error message",
            "example": "Malformed Input"
          },
          "Code": {
            "type": "integer",
            "description": "Mailpro-specific error code",
            "example": 103
          }
        }
      },
      "Webhook": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "example": 42
          },
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://example.com/hooks/mailpro"
          },
          "description": {
            "type": "string",
            "nullable": true,
            "example": "Slack integration"
          },
          "is_active": {
            "type": "boolean",
            "description": "When `false`, no event is delivered. Auto-disabled after 10 consecutive failures.",
            "example": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-07-01T21:09:52.43"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time",
            "example": "2026-07-21T18:56:08.02"
          },
          "last_delivery_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "When the most recent delivery was attempted",
            "example": "2026-07-21T18:56:07.653"
          },
          "last_delivery_status": {
            "type": "integer",
            "nullable": true,
            "description": "HTTP status returned by your endpoint on the most recent delivery",
            "example": 200
          },
          "consecutive_failures": {
            "type": "integer",
            "description": "Reset to 0 on the first success; at 10 the webhook is auto-disabled",
            "example": 0
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Subscribed event types. An empty array means every event of this API.",
            "example": [
              "sms.delivered",
              "sms.failed"
            ]
          }
        }
      },
      "WebhookList": {
        "type": "object",
        "description": "Envelope returned by `GET /webhooks`.",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Webhook"
            }
          },
          "total": {
            "type": "integer",
            "description": "Total number of webhooks on the account",
            "example": 2
          }
        }
      },
      "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": [
              "sms.delivered",
              "sms.failed"
            ]
          },
          "description": {
            "type": "string",
            "maxLength": 500,
            "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.",
                "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"
          },
          "eventId": {
            "type": "string",
            "example": "evt_8f3a9b2c4d1e5f6789abcdef01234567"
          },
          "eventType": {
            "type": "string",
            "example": "sms.delivered"
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "success",
              "retrying",
              "failed"
            ]
          },
          "statusCode": {
            "type": "integer",
            "nullable": true,
            "example": 200
          },
          "responseTimeMs": {
            "type": "integer",
            "nullable": true,
            "example": 142
          },
          "attemptCount": {
            "type": "integer"
          },
          "lastError": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "nextAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "Authentication",
      "description": "Obtain a Bearer JWT token to authenticate all other API calls. The SMS\n`/token` endpoint is a 307 redirect to the canonical `/v3/token` issuer —\nyour HTTP client follows the redirect transparently (POST method and\nbody are preserved).\n"
    },
    {
      "name": "Account",
      "description": "Check your SMS credit balance, monthly API quota, and account status."
    },
    {
      "name": "Send SMS",
      "description": "Send SMS messages — either a **single transactional SMS** to one recipient,\nor a **bulk campaign** to an entire contact list.\n"
    },
    {
      "name": "Lists",
      "description": "Manage SMS contact lists. A contact list groups phone numbers for bulk sending.\nYou can create up to 1,000 lists per account (same limit on the three Mailpro APIs and in the app).\n"
    },
    {
      "name": "Phone Numbers",
      "description": "Manage individual phone numbers within a contact list — add, update, and remove entries."
    },
    {
      "name": "Credit",
      "description": "Check the remaining SMS credit balance for your account (separate from the monthly API request quota — see `/account/limits`)."
    },
    {
      "name": "History",
      "description": "Browse the complete log of past SMS campaigns."
    },
    {
      "name": "Import",
      "description": "Upload a CSV or XLSX file to import phone numbers into a contact list in bulk.\nThe file is queued for asynchronous processing — track completion via\nthe `ImportJobId` returned or via the optional `webhook_url` callback.\n"
    },
    {
      "name": "Webhooks",
      "description": "Subscribe your server to real-time SMS event notifications. Mailpro POSTs\nan HMAC-signed JSON payload to your URL as soon as the subscribed event\nhappens. See the \"Webhooks\" section in the introduction for the full event\ncatalog, payload envelope, signature verification snippets, and retry policy.\n"
    }
  ],
  "paths": {
    "/token": {
      "post": {
        "tags": [
          "Authentication"
        ],
        "operationId": "getSmsAccessToken",
        "summary": "Get access token (307 redirect to /v3/token)",
        "security": [],
        "description": "Obtains a Bearer JWT token using Mailpro client credentials.\nPass the returned `access_token` as `Authorization: Bearer <token>` in all\nsubsequent requests. The same token is also valid on the CRM API v3 and\nthe Email v2 API.\n\n> **⚙ Implementation detail** — `POST /sms/token` is a **307 Temporary\n> Redirect** to `POST /v3/token` (configured at the IIS `web.config`\n> rewrite layer). HTTP 307 preserves both the **method** (`POST`) and\n> the **request body**, so any standards-compliant HTTP client follows\n> the redirect transparently — you keep posting to `/sms/token` and get\n> a valid JWT back. There is no duplicated token-issuance logic; the\n> SMS API never issues tokens itself.\n\n### Supported grant types\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\n### Token lifetime\n- Access token: **30 days**\n- Refresh token: **90 days**\n\nUse `grant_type=refresh_token` to renew the access token without\nre-authenticating. Store tokens server-side (Vault, env vars); never\nembed in browser JS or mobile app binaries. See the CRM API v3\n`/token` doc for the full security note.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "password",
                      "refresh_token",
                      "client_credentials"
                    ],
                    "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. Returned by `POST /v3/token` after the\ntransparent 307 follow — your HTTP client sees a normal 200 with\nthe same body shape it would receive directly from the v3 token\nendpoint.\n",
            "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
                }
              }
            }
          },
          "307": {
            "description": "Temporary Redirect to `POST /v3/token`, issued by the IIS rewrite\nrule in `web.config`. Standards-compliant HTTP clients follow this\nautomatically and preserve the original POST method + form body.\nVisible only to clients that explicitly disable redirect following\n(e.g. `curl --max-redirs 0`).\n",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                },
                "example": "https://api.mailpro.com/v3/token"
              }
            }
          },
          "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."
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/credit/get.{format}": {
      "get": {
        "tags": [
          "Credit"
        ],
        "operationId": "getSmsCredits",
        "security": [
          {
            "OAuth2": [
              "sms:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Get SMS credit balance",
        "description": "Returns the current SMS credit balance for your account.\nSMS credits are consumed at send time. One standard SMS (≤160 GSM-7 characters)\ncosts 1 credit. Longer messages or messages with special characters may cost more.\n\n> Note: this returns your **SMS send credit balance**, not your API request quota.\n> For the API quota see `GET /account/limits.{format}`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "responses": {
          "200": {
            "description": "Credit balance returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreditBalance"
                },
                "example": {
                  "TotalCredits": 5000
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/account/limits.{format}": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getSmsAccountLimits",
        "security": [
          {
            "OAuth2": [
              "account:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "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. 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- Every webhook event dispatched to a subscribed endpoint.\n\n**What is FREE (does not consume credits):**\n- `/health` endpoints.\n- Requests that fail with `401`, `403` or `429`.\n- Webhook events for clients with no matching subscription.\n\nThe quota is **shared across all 3 Mailpro APIs** (`v2`, `v3`, `sms`)\n— one client = one monthly budget across the whole platform. SMS\nsends additionally consume the dedicated SMS credit balance\n(`GET /credit/get.{format}`).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "responses": {
          "200": {
            "description": "Quota snapshot",
            "content": {
              "application/json": {
                "example": {
                  "monthlyLimit": 50000,
                  "remaining": 32847,
                  "used": 17153,
                  "isFree": false,
                  "remainedSendsForFree": null
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          },
          "404": {
            "description": "No quota record found (newly created account; row will be created on the first authenticated request)."
          }
        }
      }
    },
    "/history/list.{format}": {
      "get": {
        "tags": [
          "History"
        ],
        "operationId": "listSmsHistory",
        "security": [
          {
            "OAuth2": [
              "sms:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List SMS sending history",
        "description": "Returns a paginated log of all past SMS campaigns sent from this account.\nResults are ordered by date descending (most recent first).\nPagination: `NumberOfRecords` and `PageNumber` (headers or query, 5,000 per page by default).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "responses": {
          "200": {
            "description": "History list returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "Items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "Id": {
                            "type": "integer",
                            "description": "Identifier of the send this row belongs to",
                            "example": 583294
                          },
                          "DateSent": {
                            "type": "string",
                            "format": "date-time",
                            "description": "Delivery timestamp reported by the operator",
                            "example": "2026-07-17T18:56:39.813"
                          },
                          "Message": {
                            "type": "string",
                            "example": "Your verification code is 8472"
                          },
                          "Charge": {
                            "type": "number",
                            "description": "Credits billed for this row = **country credits × number of parts**\n(e.g. 3 for a 1-part SMS to Panama). Previously always `1`.\n",
                            "example": 3
                          },
                          "Parts": {
                            "type": "integer",
                            "description": "Number of SMS segments (GSM 03.38 parts) the message was split into.",
                            "example": 1
                          }
                        }
                      }
                    }
                  }
                },
                "example": {
                  "Items": [
                    {
                      "Id": 583294,
                      "DateSent": "2026-07-17T18:56:39.813",
                      "Message": "Your verification code is 8472",
                      "Charge": 3,
                      "Parts": 1
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/list/list.{format}": {
      "get": {
        "tags": [
          "Lists"
        ],
        "operationId": "listSmsLists",
        "security": [
          {
            "OAuth2": [
              "sms:contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List phone contact lists",
        "description": "Returns all SMS contact lists for the account.\nUse the returned `Id` values when sending bulk SMS campaigns or adding phone numbers.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "responses": {
          "200": {
            "description": "Phone lists returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "Items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PhoneList"
                      }
                    }
                  }
                },
                "example": {
                  "Items": [
                    {
                      "AddressBookId": 42,
                      "Name": "Newsletter subscribers",
                      "TotalNumberPhones": 1547
                    },
                    {
                      "AddressBookId": 43,
                      "Name": "Loyalty program",
                      "TotalNumberPhones": 823
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/list/add.{format}": {
      "post": {
        "tags": [
          "Lists"
        ],
        "operationId": "addSmsList",
        "summary": "Create a contact list",
        "description": "Creates a new SMS contact list with the given name.\nThe list starts empty — add phone numbers with `POST /phone/add`.\nMaximum 1,000 lists per account (same limit on the three Mailpro APIs and in the app). Returns HTTP 422 if the limit is reached.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "id_client",
                  "api_key",
                  "name"
                ],
                "properties": {
                  "id_client": {
                    "type": "integer",
                    "description": "Your Mailpro client ID",
                    "example": 12345
                  },
                  "api_key": {
                    "type": "string",
                    "description": "Your API key (GUID)",
                    "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  },
                  "name": {
                    "type": "string",
                    "description": "Display name for the new contact list",
                    "example": "Spring campaign 2026"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contact list created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PhoneList"
                },
                "example": {
                  "Id": 44,
                  "Name": "Spring campaign 2026",
                  "NumberPhones": 0
                }
              }
            }
          },
          "400": {
            "description": "Missing required field (name)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "Error": "Malformed Input",
                  "Code": 103
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/list/edit.{format}": {
      "post": {
        "tags": [
          "Lists"
        ],
        "operationId": "editSmsList",
        "security": [
          {
            "OAuth2": [
              "sms:contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Rename a contact list",
        "description": "Updates the display name of an existing SMS contact list.\nThe phone numbers in the list are not affected.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "id_client",
                  "api_key",
                  "address_book_id",
                  "name"
                ],
                "properties": {
                  "id_client": {
                    "type": "integer",
                    "description": "Your Mailpro client ID",
                    "example": 12345
                  },
                  "api_key": {
                    "type": "string",
                    "description": "Your API key (GUID)",
                    "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  },
                  "address_book_id": {
                    "type": "integer",
                    "description": "ID of the contact list to rename (from `/list/list`)",
                    "example": 42
                  },
                  "name": {
                    "type": "string",
                    "description": "New display name for the contact list",
                    "example": "Spring campaign 2026 — updated"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contact list renamed successfully"
          },
          "400": {
            "description": "Missing required field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/list/del.{format}": {
      "post": {
        "tags": [
          "Lists"
        ],
        "operationId": "deleteSmsList",
        "security": [
          {
            "OAuth2": [
              "sms:contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Delete a contact list",
        "description": "Permanently deletes an SMS contact list **and all the phone numbers it contains**.\nThis action cannot be undone.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "id_client",
                  "api_key",
                  "address_book_id"
                ],
                "properties": {
                  "id_client": {
                    "type": "integer",
                    "description": "Your Mailpro client ID",
                    "example": 12345
                  },
                  "api_key": {
                    "type": "string",
                    "description": "Your API key (GUID)",
                    "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  },
                  "address_book_id": {
                    "type": "integer",
                    "description": "ID of the contact list to delete",
                    "example": 42
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Contact list deleted successfully"
          },
          "400": {
            "description": "Missing address_book_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/phone/list.{format}": {
      "get": {
        "tags": [
          "Phone Numbers"
        ],
        "operationId": "listPhones",
        "security": [
          {
            "OAuth2": [
              "sms:contacts:read"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List phone numbers in a contact list",
        "description": "Returns a paginated list of phone numbers in a specific contact list.\nUse `address_book_id` to specify which list to retrieve.\nPagination: `NumberOfRecords` and `PageNumber` (5,000 per page by default, page 1).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          },
          {
            "name": "address_book_id",
            "in": "query",
            "required": true,
            "description": "ID of the contact list to retrieve phone numbers from",
            "schema": {
              "type": "integer"
            },
            "example": 42
          },
          {
            "$ref": "#/components/parameters/numberOfRecords"
          },
          {
            "$ref": "#/components/parameters/pageNumber"
          },
          {
            "$ref": "#/components/parameters/sortByName"
          }
        ],
        "responses": {
          "200": {
            "description": "Phone numbers returned successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "Items": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PhoneNumber"
                      }
                    }
                  }
                },
                "example": {
                  "Items": [
                    {
                      "Id": 1001,
                      "Number": "41791234567",
                      "FirstName": "Alice",
                      "LastName": "Dupont"
                    },
                    {
                      "Id": 1002,
                      "Number": "33612345678",
                      "FirstName": "Bob",
                      "LastName": "Martin"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/phone/add.{format}": {
      "post": {
        "tags": [
          "Phone Numbers"
        ],
        "operationId": "addPhone",
        "security": [
          {
            "OAuth2": [
              "sms:contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Add phone number(s) to a list",
        "description": "Adds one or more phone numbers to a contact list.\n\n**Single number:** use the `number` field.\n\n**Bulk import:** use the `phone_list` field with numbers separated by semicolons:\n`+41791234567;+33612345678;0049301234567`\n\nPhone numbers accept `+`, `00` or bare format — see **Phone number format**\nin the API description for the full list of accepted / rejected inputs.\nDuplicate numbers are silently skipped unless `force=1` is set.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "id_client",
                  "api_key",
                  "address_book_id"
                ],
                "properties": {
                  "id_client": {
                    "type": "integer",
                    "description": "Your Mailpro client ID",
                    "example": 12345
                  },
                  "api_key": {
                    "type": "string",
                    "description": "Your API key (GUID)",
                    "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  },
                  "address_book_id": {
                    "type": "integer",
                    "description": "ID of the contact list to add the number(s) to",
                    "example": 42
                  },
                  "number": {
                    "type": "string",
                    "description": "Single phone number. Accepts `+41…`, `0041…` or `41…` with or without\nseparators (spaces, dashes, dots, slashes, parentheses). See\n**Phone number format** in the API description.\nUse this **OR** `phone_list` — not both.\n",
                    "example": "+41791234567"
                  },
                  "phone_list": {
                    "type": "string",
                    "description": "Semicolon-separated list of phone numbers for bulk import.\nUse this **OR** `number` — not both.\nExample: `0041791234567;0033612345678`\n",
                    "example": "0041791234567;0033612345678;0049301234567"
                  },
                  "firstname": {
                    "type": "string",
                    "description": "First name of the contact (optional, single number only)",
                    "example": "Alice"
                  },
                  "lastname": {
                    "type": "string",
                    "description": "Last name of the contact (optional, single number only)",
                    "example": "Dupont"
                  },
                  "force": {
                    "type": "integer",
                    "description": "Set to `1` to add a number even if it was previously unsubscribed or already exists.\nDefault: `0` (duplicates are skipped).\n",
                    "enum": [
                      0,
                      1
                    ],
                    "default": 0,
                    "example": 0
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Phone number(s) added successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PhoneNumber"
                },
                "example": {
                  "Id": 1003,
                  "Number": "0041791234567",
                  "FirstName": "Alice",
                  "LastName": "Dupont"
                }
              }
            }
          },
          "400": {
            "description": "Missing required field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/phone/edit.{format}": {
      "post": {
        "tags": [
          "Phone Numbers"
        ],
        "operationId": "editPhone",
        "security": [
          {
            "OAuth2": [
              "sms:contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Update a phone number entry",
        "description": "Updates the phone number and/or contact details of an existing entry.\nUse `number_id` (from `/phone/list`) to identify the entry to update.\nOnly fields provided in the request are updated.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "id_client",
                  "api_key",
                  "number_id"
                ],
                "properties": {
                  "id_client": {
                    "type": "integer",
                    "description": "Your Mailpro client ID",
                    "example": 12345
                  },
                  "api_key": {
                    "type": "string",
                    "description": "Your API key (GUID)",
                    "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  },
                  "number_id": {
                    "type": "integer",
                    "description": "ID of the phone number entry to update (from `/phone/list`)",
                    "example": 1001
                  },
                  "number": {
                    "type": "string",
                    "description": "New phone number. Accepts `+41…`, `0041…` or `41…` with or without separators. See **Phone number format** in the API description.",
                    "example": "+41791234568"
                  },
                  "firstname": {
                    "type": "string",
                    "description": "New first name for the contact",
                    "example": "Alice"
                  },
                  "lastname": {
                    "type": "string",
                    "description": "New last name for the contact",
                    "example": "Dupont-Martin"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Phone number entry updated successfully"
          },
          "400": {
            "description": "Missing number_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/phone/del.{format}": {
      "post": {
        "tags": [
          "Phone Numbers"
        ],
        "operationId": "deletePhone",
        "security": [
          {
            "OAuth2": [
              "sms:contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Remove a phone number from a list",
        "description": "Permanently removes a specific phone number entry from its contact list.\nUse `number_id` (from `/phone/list`) to identify the entry.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "id_client",
                  "api_key",
                  "number_id"
                ],
                "properties": {
                  "id_client": {
                    "type": "integer",
                    "description": "Your Mailpro client ID",
                    "example": 12345
                  },
                  "api_key": {
                    "type": "string",
                    "description": "Your API key (GUID)",
                    "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  },
                  "number_id": {
                    "type": "integer",
                    "description": "ID of the phone number entry to delete (from `/phone/list`)",
                    "example": 1001
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Phone number removed successfully"
          },
          "400": {
            "description": "Missing number_id",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/send/add.{format}": {
      "post": {
        "tags": [
          "Send SMS"
        ],
        "operationId": "sendBulkSms",
        "security": [
          {
            "OAuth2": [
              "sms:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Send bulk SMS to a contact list",
        "description": "Sends an SMS message to **all phone numbers** in a contact list.\nSupports scheduling: set `scheduled_time` to a future date to send later,\nor omit it to send immediately.\n\n**Credit consumption:** credits are deducted at send time.\nLong messages (>160 GSM-7 chars) are split into multiple parts,\neach costing additional credits.\n\nThe response returns `{ \"Error\": \"Insufficient Credit\", \"Code\": 202 }` if\nthe account has insufficient credits.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "id_client",
                  "api_key",
                  "message",
                  "to"
                ],
                "properties": {
                  "id_client": {
                    "type": "integer",
                    "description": "Your Mailpro client ID",
                    "example": 12345
                  },
                  "api_key": {
                    "type": "string",
                    "description": "Your API key (GUID)",
                    "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  },
                  "message": {
                    "type": "string",
                    "description": "SMS message text. Keep under 160 characters for a single-part SMS.\nCharacters outside the GSM-7 alphabet (e.g. Chinese, Arabic, emoji)\nswitch the encoding to Unicode, reducing the limit to 70 characters per part.\n",
                    "example": "Flash sale today only — 30% off all orders. Shop now at mailpro.com/shop"
                  },
                  "to": {
                    "type": "integer",
                    "description": "ID of the contact list to send to (from `/list/list`)",
                    "example": 42
                  },
                  "scheduled_time": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Schedule the send for a future date/time (ISO 8601).\nOmit or leave empty to send immediately.\n",
                    "example": "2026-04-20T10:00:00"
                  },
                  "shorten_urls": {
                    "type": "boolean",
                    "default": false,
                    "description": "Set to `true` to replace each link of the message with a short link `https://mailp.ro/m/...`: fewer characters, so sometimes fewer billed SMS parts. A link that cannot be shortened stays as is."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bulk SMS send created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkSend"
                },
                "example": {
                  "Id": 6001,
                  "Message": "Flash sale today only — 30% off all orders. Shop now at mailpro.com/shop",
                  "ScheduledTime": "2026-04-20T10:00:00",
                  "To": [
                    "42"
                  ]
                }
              }
            }
          },
          "202": {
            "description": "Insufficient SMS credits",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "Error": "Insufficient Credit",
                  "Code": 202
                }
              }
            }
          },
          "400": {
            "description": "Missing required field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "Error": "Malformed Input",
                  "Code": 103
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/send/add_single.{format}": {
      "get": {
        "tags": [
          "Send SMS"
        ],
        "operationId": "sendSingleSmsGet",
        "security": [
          {
            "OAuth2": [
              "sms:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Send a single SMS (GET)",
        "description": "Sends a single SMS to one recipient via a GET request.\nUseful for simple integrations where POST is not available.\nCredentials and message are passed as query parameters.\n\n**Note:** avoid using GET for sensitive messages — query parameters appear in server logs.\nPrefer `POST /send/add_single` instead.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "description": "Recipient phone number. Accepts `+41…`, `0041…` or `41…` with or without separators. See **Phone number format** in the API description.",
            "schema": {
              "type": "string"
            },
            "example": "+41791234567"
          },
          {
            "name": "message",
            "in": "query",
            "required": true,
            "description": "SMS message text (max 160 chars for standard encoding)",
            "schema": {
              "type": "string"
            },
            "example": "Your appointment is confirmed for tomorrow at 10:00."
          },
          {
            "name": "scheduled_time",
            "in": "query",
            "description": "Schedule the send (ISO 8601). Omit to send immediately.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "shorten_urls",
            "in": "query",
            "description": "Set to `true` to replace each link of the message with a short link `https://mailp.ro/m/...`: fewer characters, so sometimes fewer billed SMS parts. A link that cannot be shortened stays as is.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "SMS sent (or scheduled) successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SingleSend"
                },
                "example": {
                  "Id": 5001,
                  "Message": "Your appointment is confirmed for tomorrow at 10:00.",
                  "ScheduledTime": "2026-04-15T09:00:00",
                  "To": "0041791234567",
                  "Charge": "1"
                }
              }
            }
          },
          "202": {
            "description": "Insufficient SMS credits",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "400": {
            "description": "Missing required parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      },
      "post": {
        "tags": [
          "Send SMS"
        ],
        "operationId": "sendSingleSms",
        "security": [
          {
            "OAuth2": [
              "sms:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Send a single SMS",
        "description": "Sends a single SMS message to one recipient phone number.\nSupports scheduling via `scheduled_time`.\n\n**Credit consumption:** credits are deducted at send time.\nThe `Charge` field in the response shows how many credits were consumed.\n\nThe response returns `{ \"Error\": \"Insufficient Credit\", \"Code\": 202 }` if\nthe account has insufficient credits.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "id_client",
                  "api_key",
                  "message",
                  "to"
                ],
                "properties": {
                  "id_client": {
                    "type": "integer",
                    "description": "Your Mailpro client ID",
                    "example": 12345
                  },
                  "api_key": {
                    "type": "string",
                    "description": "Your API key (GUID)",
                    "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  },
                  "message": {
                    "type": "string",
                    "description": "SMS message text",
                    "example": "Your verification code is 8472. Valid for 10 minutes."
                  },
                  "to": {
                    "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"
                  },
                  "scheduled_time": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Schedule the send for a future time. Omit to send immediately.",
                    "example": "2026-04-15T14:30:00"
                  },
                  "shorten_urls": {
                    "type": "boolean",
                    "default": false,
                    "description": "Set to `true` to replace each link of the message with a short link `https://mailp.ro/m/...`: fewer characters, so sometimes fewer billed SMS parts. A link that cannot be shortened stays as is."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SMS sent (or scheduled) successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SingleSend"
                },
                "example": {
                  "Id": 5001,
                  "Message": "Your verification code is 8472. Valid for 10 minutes.",
                  "ScheduledTime": "2026-04-15T14:30:00",
                  "To": "0041791234567",
                  "Charge": "1"
                }
              }
            }
          },
          "202": {
            "description": "Insufficient SMS credits",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "Error": "Insufficient Credit",
                  "Code": 202
                }
              }
            }
          },
          "400": {
            "description": "Missing required field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "Error": "Malformed Input",
                  "Code": 103
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/send/list.{format}": {
      "get": {
        "tags": [
          "Send SMS"
        ],
        "operationId": "listSmsSends",
        "security": [
          {
            "OAuth2": [
              "sms:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List SMS campaigns",
        "description": "Returns the list of SMS campaigns (bulk sends) for the account.\nPagination via `NumberOfRecords` and `PageNumber` headers.\n\nUse `type_to` to filter by send type:\n- `0` (default) — address-book bulk sends\n- `1` — single individual sends\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          },
          {
            "name": "type_to",
            "in": "query",
            "description": "Send type filter:\n- `0` (default) — bulk sends to a contact list\n- `1` — individual single-number sends\n",
            "schema": {
              "type": "integer",
              "enum": [
                0,
                1
              ],
              "default": 0
            },
            "example": 0
          }
        ],
        "responses": {
          "200": {
            "description": "Pending sends returned successfully (a plain array, empty when there is\nnothing waiting). Only sends still scheduled in the future are listed.\n",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SingleSend"
                  }
                },
                "example": [
                  {
                    "Id": 5001,
                    "Message": "Your verification code is 8472",
                    "ScheduledTime": "2026-04-15T09:00:00",
                    "To": "41791234567"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/send/del.{format}": {
      "post": {
        "tags": [
          "Send SMS"
        ],
        "operationId": "deleteSmsSend",
        "security": [
          {
            "OAuth2": [
              "sms:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Cancel or delete an SMS send",
        "description": "Cancels a **pending scheduled** SMS send, or deletes a **completed** one from history.\nOnly future-scheduled sends can be cancelled. Sends in progress cannot be stopped.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "id_client",
                  "api_key",
                  "id_send"
                ],
                "properties": {
                  "id_client": {
                    "type": "integer",
                    "description": "Your Mailpro client ID",
                    "example": 12345
                  },
                  "api_key": {
                    "type": "string",
                    "description": "Your API key (GUID)",
                    "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
                  },
                  "id_send": {
                    "type": "integer",
                    "description": "ID of the SMS send to cancel or delete (from `/send/list`)",
                    "example": 6001
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SMS send cancelled or deleted successfully",
            "content": {
              "application/json": {
                "example": {
                  "Result": "OK"
                }
              }
            }
          },
          "400": {
            "description": "Missing id_send",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          },
          "500": {
            "description": "Delete failed (send not found or already processed)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "Error": "Delete failed",
                  "Code": 500
                }
              }
            }
          }
        }
      }
    },
    "/send/scheduled.{format}": {
      "get": {
        "tags": [
          "Send SMS"
        ],
        "operationId": "listScheduledSms",
        "security": [
          {
            "OAuth2": [
              "sms:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "List my scheduled SMS sends",
        "description": "Returns the SMS sends (single or campaign) scheduled for a future date that\nhave not yet been dispatched. Useful before calling `/cancel` or `/reschedule`.\n\nRecords are sorted by `scheduled_time` ascending.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          },
          {
            "name": "id_client",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "example": 12345
          },
          {
            "name": "api_key",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
          },
          {
            "$ref": "#/components/parameters/numberOfRecords"
          },
          {
            "$ref": "#/components/parameters/pageNumber"
          }
        ],
        "responses": {
          "200": {
            "description": "List of scheduled SMS sends",
            "content": {
              "application/json": {
                "example": [
                  {
                    "Id": 9001,
                    "Message": "Black Friday: 30% off until midnight.",
                    "ScheduledTime": "2026-11-27T12:00:00",
                    "To": "+33671234567"
                  }
                ]
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/send/{id_send}/cancel.{format}": {
      "post": {
        "tags": [
          "Send SMS"
        ],
        "operationId": "cancelScheduledSms",
        "security": [
          {
            "OAuth2": [
              "sms:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Cancel a scheduled SMS send",
        "description": "Cancels a scheduled SMS that has not yet been dispatched. If the send has\nalready been processed (or doesn't exist / belongs to another client),\nreturns **409 Conflict**.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          },
          {
            "name": "id_send",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "example": 9001
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "id_client",
                  "api_key"
                ],
                "properties": {
                  "id_client": {
                    "type": "integer",
                    "example": 12345
                  },
                  "api_key": {
                    "type": "string",
                    "example": "a1b2c3d4-..."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Send cancelled",
            "content": {
              "application/json": {
                "example": {
                  "Result": "OK",
                  "Cancelled": 9001
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          },
          "409": {
            "description": "Already dispatched or not found",
            "content": {
              "application/json": {
                "example": {
                  "error": "conflict",
                  "message": "Send not found, not owned by client, or already dispatched."
                }
              }
            }
          }
        }
      }
    },
    "/send/{id_send}/reschedule.{format}": {
      "patch": {
        "tags": [
          "Send SMS"
        ],
        "operationId": "rescheduleSms",
        "security": [
          {
            "OAuth2": [
              "sms:send"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Reschedule a scheduled SMS send",
        "description": "Updates the planned date of a scheduled SMS that has not yet been dispatched.\nNew date must be **in the future** and **within 90 days**.\n\n**Date field name:** the API accepts `scheduled_time` (primary) as well as\nthe legacy aliases `PlanDate`, `date_planned`, and `DatePlanned`. Provide\nany one of them — the first non-null is used.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          },
          {
            "name": "id_send",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "example": 9001
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "id_client",
                  "api_key"
                ],
                "description": "Provide one of `scheduled_time`, `PlanDate`, `date_planned`, or `DatePlanned`.",
                "properties": {
                  "id_client": {
                    "type": "integer",
                    "example": 12345
                  },
                  "api_key": {
                    "type": "string",
                    "example": "a1b2c3d4-..."
                  },
                  "scheduled_time": {
                    "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."
                  },
                  "date_planned": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Legacy alias accepted for backward compatibility."
                  },
                  "DatePlanned": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Legacy alias accepted for backward compatibility."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Send rescheduled",
            "content": {
              "application/json": {
                "example": {
                  "Result": "OK",
                  "Id": 9001,
                  "NewScheduledTime": "2026-12-31T14:30:00"
                }
              }
            }
          },
          "400": {
            "description": "Missing or invalid scheduled_time"
          },
          "401": {
            "description": "Invalid credentials"
          },
          "409": {
            "description": "Already dispatched, not found, or new date out of range",
            "content": {
              "application/json": {
                "example": {
                  "error": "conflict",
                  "message": "Reschedule failed — send not found, already dispatched, or new date out of range."
                }
              }
            }
          }
        }
      }
    },
    "/import/upload.{format}": {
      "post": {
        "tags": [
          "Import"
        ],
        "operationId": "uploadImportFile",
        "security": [
          {
            "OAuth2": [
              "sms:contacts:write"
            ]
          },
          {
            "bearerAuth": []
          },
          {
            "IdClientHeader": [],
            "ApiKeyHeader": []
          },
          {
            "IdClientQuery": [],
            "ApiKeyQuery": []
          }
        ],
        "summary": "Upload a phone-numbers file (CSV / XLSX) for bulk import",
        "description": "Upload a CSV or XLSX file to import phone numbers into a contact list.\nThe file is stored and queued for asynchronous processing — the endpoint\nreturns an `ImportJobId` immediately.\n\n**Request type:** `multipart/form-data`\n\n**Required form fields:**\n- `id_client` (integer) — your Mailpro client ID\n- `api_key` (string) — your API key\n- `id_list` (integer) — target contact list ID\n- `file` (binary) — the CSV or XLSX file to upload\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 = headers (one must be `Phone` or `PhoneNumber` — case-insensitive)\n- Numbers in international format (E.164), e.g. `+41791234567`\n- Max file size: 50 MB\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/format"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "id_list",
                  "file"
                ],
                "properties": {
                  "id_list": {
                    "type": "integer",
                    "format": "int64",
                    "description": "ID of the destination contact list",
                    "example": 11088
                  },
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "CSV or XLSX file (UTF-8, max 50 MB)"
                  },
                  "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",
                  "ImportJobId": 15,
                  "InternalName": "230575-20260420142616-phones.csv"
                }
              }
            }
          },
          "400": {
            "description": "No file uploaded or missing id_list"
          },
          "401": {
            "description": "Invalid credentials"
          },
          "500": {
            "description": "Server or storage error"
          }
        }
      }
    },
    "/webhooks": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/sms",
          "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": "Webhooks of the account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookList"
                },
                "example": {
                  "items": [
                    {
                      "id": 14,
                      "url": "https://example.com/hooks/mailpro",
                      "description": "Slack integration",
                      "is_active": true,
                      "created_at": "2026-07-01T21:09:52.43",
                      "updated_at": "2026-07-21T18:56:08.02",
                      "last_delivery_at": "2026-07-21T18:56:07.653",
                      "last_delivery_status": 200,
                      "consecutive_failures": 0,
                      "events": [
                        "sms.delivered",
                        "sms.failed"
                      ]
                    }
                  ],
                  "total": 1
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      },
      "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": [
                  "sms.delivered",
                  "sms.failed"
                ],
                "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": [
                    "sms.delivered",
                    "sms.failed"
                  ],
                  "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 credentials"
          }
        }
      }
    },
    "/webhooks/{id}": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/sms",
          "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"
          }
        }
      }
    },
    "/webhooks/{id}/test": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/sms",
          "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"
                  }
                }
              },
              "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"
          }
        }
      }
    },
    "/webhooks/{id}/rotate-secret": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/sms",
          "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"
          }
        }
      }
    },
    "/webhooks/{id}/deliveries": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/sms",
          "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"
          }
        }
      }
    },
    "/webhooks/{id}/deliveries/{deliveryId}/replay": {
      "servers": [
        {
          "url": "https://webhook.mailpro.com/api/sms",
          "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(same backoff schedule, same 10-failure auto-disable rule).\n",
        "tags": [
          "Webhooks"
        ],
        "responses": {
          "202": {
            "description": "Replay queued",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "new_delivery_id": {
                      "type": "integer",
                      "format": "int64",
                      "example": 12999
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Webhook or delivery not found"
          }
        }
      }
    }
  }
}