{
  "info": {
    "name": "5c SMS & Dingo Mail API",
    "_postman_id": "8f4d2a1c-3b6e-4f7a-9c0d-5e8b1a2c3d4e",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "description": "# 5c SMS & Dingo Mail API\n\nOne API for business messaging across two channels: SMS through **5c SMS** and email through **Dingo Mail**. Send and track SMS, hold two-way conversations, read inbound messages, and manage opt-outs, sender IDs and virtual numbers. Send transactional and bulk email from your own verified sending domains, with delivery status on every message. Endpoints are secure, JSON over HTTPS, and grouped in the sidebar by area.\n\n## Getting started\n\n**Step 1: Generate your API key.**\n1. Go to https://www.5centsms.com.au/dashboard/api\n2. In **API Key Management**, set a **Key Alias** to identify the key.\n3. Click **Create New API Key**.\n4. Copy the generated **Key ID** and **Key Secret**.\n\nThe same key authenticates both the 5c SMS endpoints and the Dingo Mail email endpoints.\n\n> **Important:** Store your credentials securely. The Key Secret is shown only once and cannot be retrieved again.\n\n**Step 2: Send your first SMS.**\n```bash\ncurl -X POST {{base_url_sms}}/sms \\\n  -H \"Authorization: Bearer Paste Key ID here:Paste Key Secret here\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"sender\": \"0404123123\",\n    \"to\": \"Paste recipient number here\",\n    \"message\": \"Hello World\"\n  }'\n```\n\n**Step 3: Send your first email.**\n```bash\ncurl -X POST {{base_url_dingo}}/email \\\n  -H \"Authorization: Bearer Paste Key ID here:Paste Key Secret here\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"SenderEmail\": \"news@mail.example.com\",\n    \"SenderName\": \"Example\",\n    \"Subject\": \"Hello\",\n    \"Text\": \"Hello World\",\n    \"Recipient\": \"Paste recipient email here\"\n  }'\n```\n\nEmail sends from a verified sending domain. Register one with **Create Domain** (under Email Domains) or in the dashboard, then use any address at that domain as the `SenderEmail`.\n\n## Authentication\n\nEvery request authenticates with your `key-id` and `key-secret`. Set them once as the `key_id` / `key_secret` collection variables (under the collection's **Variables** tab) and they apply to every request here.\n\nSend the pair in the `Authorization` header as a `Bearer` token, id and secret joined by a colon. This collection is preconfigured to do this on every request:\n\n```bash\ncurl -X POST {{base_url_sms}}/sms \\\n  -H \"Authorization: Bearer your-key-id:your-key-secret\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{ \"sender\": \"0404123123\", \"to\": \"0412333555\", \"message\": \"Hello World\" }'\n```\n\nAll requests must use **HTTPS** — a plain HTTP request is rejected with `400 HTTPS Required`.\n\n## Responses & errors\n\nEvery response is JSON and carries an `error` field. The HTTP status follows from it:\n\n- **HTTP 200** — success; `error` is an empty string (`\"\"`).\n- **HTTP 400** — the request failed; the reason is in `error`.\n- **HTTP 401** — authentication failed (see the table below).\n- **HTTP 503** — the API is in maintenance; retry shortly.\n\nAn unsupported method/path combination returns `Unsupported method. Please see our API Docs`.\n\n**Authentication errors (HTTP 401):**\n\n| Error | Cause |\n| --- | --- |\n| `Failed (Invalid Key ID)` | The `key-id` is missing or not recognised. |\n| `Failed (Invalid Key Secret)` | The `key-secret` does not match the key. |\n| `Failed (Invalid API ID or Key)` | The key id/secret pair is invalid. |\n| `Failed (Invalid Username or API Key)` | The key id/secret pair failed verification. |\n\n## API hosts\n\nTwo base URLs back this collection, preset as variables:\n\n- `{{base_url_sms}}` (5c SMS): SMS, Conversations, Account, Logins, Sender IDs, Virtual Numbers, SMS Templates.\n- `{{base_url_dingo}}` (Dingo Mail): Email, Email Domains, and Email Templates.\n\n## Pagination\n\nList endpoints return results **newest-first, one page at a time**. To fetch the next page, pass the **last item's `id`** as the `after` query parameter. `after` is a 24-character hex cursor (a MongoDB ObjectId), **not** an offset or page number.\n\nEach list response includes `next_page` — a ready-to-use relative path already containing the `after` value for the following page — and `count` (items on the current page). Follow `next_page` until it is absent or empty.\n\n`next_page` is a **root-relative** path beginning with `/` (for example `/api/v5/sms?after=...`). Resolve it against the API host (scheme + host of the base URL), not against the current request path, then re-send with the same credentials.\n\nCursor-paginated endpoints (Conversations) return `next_cursor` instead: pass it back as the `before` parameter, and stop when it is null.\n\nPage sizes are fixed per endpoint:\n\n| Endpoint | Page size |\n| --- | --- |\n| List Emails | 20 (override with `limit`, 1–100) |\n| List Email Templates | 20 (override with `limit`, 1–100) |\n| List SMS Templates | 20 (override with `limit`, 1–100) |\n| List Inbound Messages | 50 |\n| List Opt-outs | 1000 |\n| List Campaigns | 200 (override with `limit`, 1–200) |\n| Get Campaign Recipients | 100 (override with `limit`, 1–1000) |\n\nA malformed `after` returns HTTP 400 — `Failed (Invalid Page)` on the SMS / Inbox / Opt-out endpoints, `Invalid after parameter` on List Emails, List Email Templates, List SMS Templates, List Campaigns and Get Campaign Recipients (all of which also reject a bad `limit` with `Invalid limit parameter`). List Campaigns and Get Campaign Recipients emit `next_page` only when a full page was returned, so paging stops when it is absent. Logins, Sender IDs, Virtual Numbers, and Email Domains return full unpaginated lists.\n\n## Message status codes\n\nThe `status` field on a message maps to a human-readable `status_text`. For the full list of status codes and their meanings, see [Message Status List](https://www.5centsms.com.au/web/status).\n\n## Data retention\n\nMessage data is retained for 365 days from the date of processing, after which it is permanently purged. Contact support if you require a different retention timeframe.\n\n## Using Postman\n\nDownload this collection and import it into Postman (**Import → File**, or paste the raw link). Set the `key_id` and `key_secret` collection variables once under the collection's **Variables** tab, then open any request and click **Send**. Each request's **Documentation** pane (the right-hand panel) shows its parameters, response fields, and errors."
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{key_id}}:{{key_secret}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "base_url_sms",
      "value": "https://www.5centsms.com.au/api/v5"
    },
    {
      "key": "base_url_dingo",
      "value": "https://api.dingomail.com.au/api/v5"
    },
    {
      "key": "key_id",
      "value": ""
    },
    {
      "key": "key_secret",
      "value": ""
    }
  ],
  "item": [
    {
      "name": "SMS",
      "item": [
        {
          "name": "Send SMS",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/sms",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "sms"
              ]
            },
            "description": "Send a single SMS to one or more recipients.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `sender` | string | Yes | Sender ID. Max 13 characters; max 11 if alphanumeric ([0-9a-zA-Z] only). |\n| `to` | string | Yes | Recipient number(s), comma-separated. International or 04 format. |\n| `message` | string | Yes | Message content. Hex-encoded UTF-16 when `unicode` is true. Otherwise sent as GSM-7, which means the text is transliterated before sending and the send fails if any character survives that. See Notes. |\n| `test` | boolean | No | Simulate the send — no SMS delivered, no credits charged. Default false. |\n| `unicode` | boolean | No | Send as UTF-16; `message` must be hex-encoded. Must be enabled on your account. Deprecated for new accounts; use a Unified Virtual Number for rich text messaging. Default false. |\n| `schedule` | integer | No | UNIX epoch seconds for future delivery, max 356 days ahead. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `messages` | array | One entry per recipient. |\n| `messages[].destination` | string | Recipient number. |\n| `messages[].id` | string | Message id. |\n| `messages[].status` | integer | Status code (see Introduction). |\n| `messages[].status_text` | string | Human-readable status. |\n| `messages[].credits` | number | Credits charged. |\n| `messages[].schedule` | integer | Scheduled epoch — only when the message is scheduled (status 1005). |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Failed (Invalid Sender ID)` | 400 | `sender` missing, >13 chars, or alphanumeric >11 chars / invalid characters. |\n| `Failed (Invalid Destination)` | 400 | `to` missing. |\n| `Failed (Invalid Message)` | 400 | `message` missing. |\n\n#### Notes\n- GSM-7 messages are transliterated before they are sent. Smart quotes become plain quotes, dashes become hyphens, an ellipsis becomes three dots, and accented letters outside the GSM alphabet lose their accents, so `Álvaro` is delivered as `Alvaro`. The bytes you post are not always the bytes that arrive. Accented letters that are already in the GSM alphabet (`é`, `ü`, `ø`, `à`, `ñ`) are left exactly as sent.\n- Any character still outside the GSM alphabet after transliteration is rejected. The request returns **200**, but that recipient's `messages[].status` is **1701** (`Failed (Invalid Characters in Message)`), nothing is delivered and no credit is charged. This covers emoji, CJK, and symbols with no GSM equivalent such as `°`, `‰`, `✓` and `¢`. Set `unicode` to `true` to send them as UTF-16 instead, or call `POST /segments` first to see exactly which characters a body would fail on.\n- With `unicode`, emoji and non-GSM text are supported; e.g. `Testing🎉` hex-encodes to `00540065007300740069006E0067D83CDF89`.\n- Use `test: true` to verify integration without sending or charging.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"sender\": \"0404123123\",\n  \"to\": \"0412333555\",\n  \"message\": \"Hello World\",\n  \"test\": false,\n  \"unicode\": false,\n  \"schedule\": 0\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"messages\": [\n    {\n      \"destination\": \"0412333555\",\n      \"id\": \"683554c596937cc4b90f5cf7\",\n      \"status\": 1011,\n      \"status_text\": \"Sending...\",\n      \"credits\": 1\n    }\n  ]\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Failed (Invalid Sender ID)\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "List Sent SMS",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/sms",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "sms"
              ],
              "query": [
                {
                  "key": "after",
                  "value": "",
                  "description": "24-hex cursor — the `id` of the last message on the previous page.",
                  "disabled": true
                }
              ]
            },
            "description": "List your sent messages, newest first.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `messages` | array | Sent messages this page. |\n| `messages[].destination` | string | Recipient number. |\n| `messages[].sender` | string | Sender ID the message was sent from (mobile number, virtual number, or alphanumeric sender ID). |\n| `messages[].id` | string | Message id. |\n| `messages[].status` | integer | Status code (see Introduction). |\n| `messages[].status_text` | string | Human-readable status. |\n| `messages[].message_text` | string | Message content. |\n| `messages[].credits` | number | Credits charged. |\n| `messages[].send_timestamp` | integer | Epoch seconds when sent. |\n| `messages[].delivery_timestamp` | integer | Epoch seconds when delivery confirmed. |\n| `messages[].delivery_network` | string | Delivering network, when known. |\n| `next_page` | string | Relative path for the next page. Omitted when there are no further results. |\n| `count` | integer | Messages on this page. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Failed (Invalid Page)` | 400 | `after` is not a valid 24-hex cursor. |\n\n#### Notes\n- See **Pagination** in the Introduction for the `after` cursor convention.\n- `next_page` is omitted when there are no further results.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"messages\": [\n    {\n      \"destination\": \"0412333555\",\n      \"sender\": \"5CENTSMS\",\n      \"id\": \"683554c596937cc4b90f5cf7\",\n      \"status\": 1002,\n      \"status_text\": \"Sent (Delivery Confirmed)\",\n      \"message_text\": \"Hello World\",\n      \"credits\": 1,\n      \"send_timestamp\": 1717000000,\n      \"delivery_timestamp\": 1717000020,\n      \"delivery_network\": \"Telstra\"\n    },\n    {\n      \"destination\": \"0412333556\",\n      \"sender\": \"0428000000\",\n      \"id\": \"683554c596937cc4b90f5cf6\",\n      \"status\": 1001,\n      \"status_text\": \"Sent\",\n      \"message_text\": \"Your verification code is 4821\",\n      \"credits\": 1,\n      \"send_timestamp\": 1716999900,\n      \"delivery_timestamp\": 0,\n      \"delivery_network\": \"Optus\"\n    },\n    {\n      \"destination\": \"0412333557\",\n      \"sender\": \"SHOPCO\",\n      \"id\": \"683554c596937cc4b90f5cf5\",\n      \"status\": 1002,\n      \"status_text\": \"Sent (Delivery Confirmed)\",\n      \"message_text\": \"Your order has shipped\",\n      \"credits\": 1,\n      \"send_timestamp\": 1716999800,\n      \"delivery_timestamp\": 1716999830,\n      \"delivery_network\": \"Vodafone\"\n    }\n  ],\n  \"next_page\": \"/api/v5/sms?after=683554c596937cc4b90f5cf5\",\n  \"count\": 3\n}"
            },
            {
              "name": "200 OK (Last Page)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"messages\": [\n    {\n      \"destination\": \"0412333558\",\n      \"sender\": \"5CENTSMS\",\n      \"id\": \"683554c596937cc4b90f5cf4\",\n      \"status\": 1002,\n      \"status_text\": \"Sent (Delivery Confirmed)\",\n      \"message_text\": \"Appointment reminder\",\n      \"credits\": 1,\n      \"send_timestamp\": 1716999700,\n      \"delivery_timestamp\": 1716999730,\n      \"delivery_network\": \"Telstra\"\n    }\n  ],\n  \"count\": 1\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Get SMS Status",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/sms/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "sms",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Message id."
                }
              ]
            },
            "description": "Fetch the current status and details of a single sent message.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `message` | object | The message (same fields as List Sent SMS). |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Failed (Unauthorised)` | 400 | Message not found, or not owned by this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": {\n    \"destination\": \"0412333555\",\n    \"sender\": \"5CENTSMS\",\n    \"id\": \"683554c596937cc4b90f5cf7\",\n    \"status\": 1002,\n    \"status_text\": \"Sent (Delivery Confirmed)\",\n    \"message_text\": \"Hello World\",\n    \"credits\": 1,\n    \"send_timestamp\": 1717000000,\n    \"delivery_timestamp\": 1717000020,\n    \"delivery_network\": \"Telstra\"\n  }\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Cancel / Delete SMS",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/sms/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "sms",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Message id."
                }
              ]
            },
            "description": "Cancel a still-scheduled message (status 1005), or delete a sent message's record.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Failed (Unauthorised)` | 400 | Message not found, or not owned by this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "List Inbound Messages",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/inbox",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "inbox"
              ],
              "query": [
                {
                  "key": "after",
                  "value": "",
                  "description": "24-hex cursor - the `id` of the last message on the previous page. Returns messages older than that id, matching this list's newest-first order.",
                  "disabled": true
                },
                {
                  "key": "include_archived",
                  "value": "1",
                  "description": "Optional. Set to 1 to include archived messages. Archived messages are excluded by default.",
                  "disabled": true
                }
              ]
            },
            "description": "List inbound (received) messages, newest first (50 per page).\n\n#### Query parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `after` | string | No | 24-hex cursor - the `id` of the last message on the previous page. Returns messages older than that id. |\n| `include_archived` | boolean | No | Set to `1` to include archived messages. Excluded by default. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `messages` | array | Inbound messages this page. |\n| `messages[].id` | string | Inbound message id. |\n| `messages[].to` | string | Your number that received it. |\n| `messages[].from` | string | Sender number. |\n| `messages[].message` | string | Message content. |\n| `messages[].date` | integer | Epoch seconds received. |\n| `messages[].contact_name` | string | Matched contact name, when known. |\n| `messages[].archived` | boolean | True when the account has archived this message. Only ever `true` when `include_archived` is set. |\n| `next_page` | string | Relative path for the next page. Always present on this endpoint - stop paging when `messages` is empty (`count` is 0). It carries `include_archived` when that flag was set. |\n| `count` | integer | Messages on this page. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Failed (Invalid Page)` | 400 | `after` is not a valid 24-hex cursor. |\n\n#### Notes\n- Page size is fixed at 50. Follow `next_page` (which carries the `after` cursor) until a page comes back with an empty `messages` array.\n- The list is newest-first and `after` pages backwards through it: each page returns the 50 messages older than the cursor.\n- Archived messages are excluded from this list. Pass `include_archived=1` to include them. Archive with `POST /inbox/{id}`. Archiving cannot be reversed.\n- Archiving does not delete: an archived message keeps its text, its attachments, and its place in the conversation, and its 356-day retention is unchanged.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"messages\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf7\",\n      \"to\": \"0400000000\",\n      \"from\": \"0412333555\",\n      \"message\": \"STOP\",\n      \"date\": 1717000000,\n      \"contact_name\": \"Jane\",\n      \"archived\": false\n    },\n    {\n      \"id\": \"683554c596937cc4b90f5cf3\",\n      \"to\": \"0400000000\",\n      \"from\": \"0412999888\",\n      \"message\": \"Yes, please book me in\",\n      \"date\": 1716999500,\n      \"contact_name\": \"Tom\",\n      \"archived\": false\n    }\n  ],\n  \"next_page\": \"/api/v5/inbox?after=683554c596937cc4b90f5cf3\",\n  \"count\": 2\n}"
            },
            {
              "name": "200 OK (Including Archived)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"messages\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf7\",\n      \"to\": \"0400000000\",\n      \"from\": \"0412333555\",\n      \"message\": \"STOP\",\n      \"date\": 1717000000,\n      \"contact_name\": \"Jane\",\n      \"archived\": true\n    },\n    {\n      \"id\": \"683554c596937cc4b90f5cf3\",\n      \"to\": \"0400000000\",\n      \"from\": \"0412999888\",\n      \"message\": \"Yes, please book me in\",\n      \"date\": 1716999500,\n      \"contact_name\": \"Tom\",\n      \"archived\": false\n    }\n  ],\n  \"next_page\": \"/api/v5/inbox?after=683554c596937cc4b90f5cf3&include_archived=1\",\n  \"count\": 2\n}"
            },
            {
              "name": "200 OK (Last Page)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"messages\": [],\n  \"next_page\": \"/api/v5/inbox?after=\",\n  \"count\": 0\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Delete Inbound Message",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/inbox/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "inbox",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Inbound message id."
                }
              ]
            },
            "description": "Delete a single inbound message record.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Failed (Invalid message ID format)` | 400 | The `id` path segment is not a 24-character hexadecimal id. |\n| `Failed (Unauthorised)` | 400 | Message not found, or not owned by this account. |\n\n#### Notes\n- This permanently removes the message document. To keep the message but hide it from `GET /inbox`, use `POST /inbox/{id}` with `archived: true` instead.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Archive Inbound Message",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": {
              "raw": "{{base_url_sms}}/inbox/:id",
              "host": [ "{{base_url_sms}}" ],
              "path": [ "inbox", ":id" ],
              "variable": [
                { "key": "id", "value": "", "description": "Inbound message id." }
              ]
            },
            "description": "Archive one received message. Archiving hides the message from `GET /inbox` unless that call passes `include_archived=1`; it never deletes the message or its attachments, does not change its 356-day retention, and leaves it visible in the conversation view.\n\n**Archiving is permanent.** There is no unarchive operation, on this endpoint or in the dashboard. Sending `archived: false` returns `Unarchiving is not supported`.\n\n#### Request body\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `archived` | boolean | Yes | Must be truthy (`true`, `1`, `\"true\"`). A falsy value is rejected - archiving cannot be reversed. |\n\n#### Path parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `id` | string | Yes | 24-hex inbound message id. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `message` | string | `Inbound message archived`. |\n| `id` | string | The inbound message id acted on. |\n| `archived` | boolean | Always `true` on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid inbound message ID` | 400 | `id` is not a valid 24-hex id. |\n| `archived Required. Please see our API Docs` | 400 | The body has no `archived` field. |\n| `Unarchiving is not supported` | 400 | `archived` was sent as `false`, `0`, `\"false\"`, or `null`. |\n| `Failed (Unauthorised)` | 400 | No such inbound message, or not owned by this account. |\n| `Unsupported method. Please see our API Docs` | 400 | POSTed to `/inbox` with no id. |\n\n#### Notes\n- Idempotent: re-archiving an already-archived message succeeds and returns the same body. It refreshes the stored archive timestamp to the time of the latest request.\n- Archiving is free and does not touch the message balance.\n- To remove the message entirely instead, use `DELETE /inbox/{id}`.",
            "body": {
              "mode": "raw",
              "options": { "raw": { "language": "json" } },
              "raw": "{\n  \"archived\": true\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": \"Inbound message archived\",\n  \"id\": \"683554c596937cc4b90f5cf7\",\n  \"archived\": true\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Unarchiving is not supported\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "List Opt-outs",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/optouts",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "optouts"
              ],
              "query": [
                {
                  "key": "after",
                  "value": "",
                  "description": "24-hex cursor — the `id` of the last record on the previous page.",
                  "disabled": true
                }
              ]
            },
            "description": "List numbers that have opted out, newest first (1000 per page).\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `numbers` | array | Opted-out numbers this page. |\n| `numbers[].id` | string | Opt-out record id. |\n| `numbers[].number` | string | Opted-out number. |\n| `numbers[].timestamp` | integer | Epoch seconds of opt-out. |\n| `count` | integer | Records on this page. |\n| `next_page` | string | Relative path for the next page. Always present on this endpoint — stop paging when `numbers` is empty (`count` is 0). |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Failed (Invalid Page)` | 400 | `after` is not a valid 24-hex cursor. |\n\n#### Notes\n- Page size is fixed at 1000. Follow `next_page` (which carries the `after` cursor) until a page comes back with an empty `numbers` array.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"numbers\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf7\",\n      \"number\": \"0412333555\",\n      \"timestamp\": 1717000000\n    },\n    {\n      \"id\": \"683554c596937cc4b90f5cf2\",\n      \"number\": \"0412333777\",\n      \"timestamp\": 1716999000\n    }\n  ],\n  \"count\": 2,\n  \"next_page\": \"/api/v5/optouts?after=683554c596937cc4b90f5cf2\"\n}"
            },
            {
              "name": "200 OK (Last Page)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"numbers\": [],\n  \"count\": 0,\n  \"next_page\": \"/api/v5/optouts?after=\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Delete Opt-out",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/optouts/:number",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "optouts",
                ":number"
              ],
              "variable": [
                {
                  "key": "number",
                  "value": "",
                  "description": "The opted-out number to remove."
                }
              ]
            },
            "description": "Remove a number from your opt-out list.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `message` | string | Confirmation message. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": \"Number removed.\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "List Status Codes",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/statuslist",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "statuslist"
              ]
            },
            "description": "Return the full catalogue of message status codes and their descriptions. The list is global (not account-specific) and matches the codes returned in the `status` field of `GET /sms` and `GET /sms/{id}` and the descriptions used in the Dashboard.\n\nAuthentication is still required (see the intro's **Authentication** section) so the call is rate-limited and logged like every other v5 endpoint.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `status_codes` | array | One object per status code. |\n| `status_codes[].code` | number | Numeric status code (e.g. `1002`). |\n| `status_codes[].description` | string | Human-readable description (e.g. `Sent (Delivery Confirmed)`). |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Unsupported method. Please see our API Docs` | 400 | The endpoint was called with `POST` or `DELETE`. |\n\nAuthentication errors are documented once in the collection intro (401 auth-error table).",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"status_codes\": [\n    { \"code\": 1000, \"description\": \"Sending... (Queued)\" },\n    { \"code\": 1001, \"description\": \"Sent\" },\n    { \"code\": 1002, \"description\": \"Sent (Delivery Confirmed)\" }\n  ]\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Calculate SMS Segments",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/segments",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "segments"
              ]
            },
            "description": "Measure a message body without sending it. Returns the length we will charge on, how many segments it splits into, the characters we will substitute, and any characters that would cause the send to be rejected. This is the same calculation the send endpoint and the dashboard character counters use, so the segment count returned here is the number of credits a single Australian recipient will cost.\n\nA GSM-7 message is one segment up to 160 characters, and gains a segment at 305, 457 and 609. A Unicode (UCS-2) message is one segment up to 70, and gains a segment at 124, 177 and 230. Five segments is the maximum either way: 765 characters GSM, 335 Unicode.\n\nSome characters count for more than one. The nine GSM 03.38 extension characters (`|` `^` `€` `{` `}` `[` `]` `~` `\\`) each occupy **two** characters in a GSM-7 message, because they are transmitted as an escape byte plus the character; they occupy one in Unicode. Everything else costs one, including accented letters that are in the GSM alphabet (`é`, `ü`, `ø`) and line breaks. `message_length` is always the figure you will be charged on, so use it rather than counting the string yourself, and note that it is a count of characters as the carrier encodes them, not of bytes: a UTF-8 byte count over-states it.\n\nBefore measuring a GSM body we transliterate it: smart quotes become plain quotes, dashes become hyphens, and accented letters outside the GSM alphabet lose their accents. `cleaned_message` shows the result. Any character still outside the GSM alphabet after that is listed in `non_gsm_characters`, and a send of the same body would be rejected with status 1701 (Failed - Invalid Characters in Message). Set `unicode` to `true` to send those characters as UCS-2 instead.\n\nNo message is sent, no credit is charged, and no account state changes.\n\n#### Request parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `message` | string | Yes | The message body to measure. Maximum 10000 characters. |\n| `unicode` | boolean | No | Measure as Unicode (UCS-2) instead of GSM-7. No transliteration is applied. Defaults to `false`. |\n| `optout` | boolean | No | Include the length of the opt-out link the send path appends. Defaults to `false`. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `message_length` | integer | Chargeable length: characters after transliteration, plus one extra for each GSM extension character, plus `optout_length`. May be larger than the number of characters you typed. |\n| `segments` | integer | Number of segments, 1 to 5. Equals the credits charged per Australian recipient. |\n| `encoding` | string | `GSM` or `UCS-2`. |\n| `extended_characters` | integer | Count of GSM extension characters, ignoring any inside a `{{field}}` or `[#field#]` placeholder. |\n| `non_gsm_characters` | array | Distinct characters that cannot be sent as GSM-7 even after transliteration. Empty when the body is sendable. Always empty when `unicode` is true. |\n| `cleaned_message` | string | The body as it would be transmitted, after transliteration. Identical to `message` when `unicode` is true. |\n| `max_length` | integer | Longest body we accept: 765 for GSM, 335 for Unicode, both being five full segments. |\n| `characters_remaining` | integer | `max_length` minus `message_length`. Negative when the body is over the limit. |\n| `optout_length` | integer | Characters added for the opt-out link, or 0 when `optout` is false. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Message text is required` | 400 | `message` missing, not a string, or empty. |\n| `Message text is too long (max 10000 characters)` | 400 | `message` longer than 10000 characters. |\n| `Unsupported method. Please see our API Docs` | 400 | Called with GET or DELETE. |\n\nAuthentication errors are documented once in the collection intro (401 auth-error table).",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"message\": \"Your order {1234} is ready. Café closes at 5pm.\",\n  \"unicode\": false,\n  \"optout\": false\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message_length\": 49,\n  \"segments\": 1,\n  \"encoding\": \"GSM\",\n  \"extended_characters\": 2,\n  \"non_gsm_characters\": [],\n  \"cleaned_message\": \"Your order {1234} is ready. Café closes at 5pm.\",\n  \"max_length\": 765,\n  \"characters_remaining\": 716,\n  \"optout_length\": 0\n}"
            },
            {
              "name": "200 OK (would be rejected)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message_length\": 14,\n  \"segments\": 1,\n  \"encoding\": \"GSM\",\n  \"extended_characters\": 0,\n  \"non_gsm_characters\": [\"\\ud83d\\ude00\"],\n  \"cleaned_message\": \"See you at 5 \\ud83d\\ude00\",\n  \"max_length\": 765,\n  \"characters_remaining\": 751,\n  \"optout_length\": 0\n}"
            },
            {
              "name": "200 OK (multipart, Unicode)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message_length\": 145,\n  \"segments\": 3,\n  \"encoding\": \"UCS-2\",\n  \"extended_characters\": 0,\n  \"non_gsm_characters\": [],\n  \"cleaned_message\": \"...\",\n  \"max_length\": 335,\n  \"characters_remaining\": 190,\n  \"optout_length\": 0\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Message text is required\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "SMS Templates",
      "item": [
        {
          "name": "List SMS Templates",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/smstemplates?limit=2",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "smstemplates"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "2",
                  "description": "Rows per page, 1 to 100. Defaults to 20."
                },
                {
                  "key": "after",
                  "value": null,
                  "disabled": true,
                  "description": "Cursor: id of the last template on the previous page."
                }
              ]
            },
            "description": "List the saved SMS templates on your account, newest first. Templates can be created and edited in the dashboard (Templates) or over this API.\n\n#### Query parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `limit` | integer | No | Rows per page, 1 to 100. Defaults to 20. |\n| `after` | string | No | Cursor: the `id` of the last template on the previous page. Returns older templates only. |\n\n`next_page` is present only when a full page was returned, so paginate until it is absent. An account can hold at most 100 SMS templates.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `templates` | array | One object per template. |\n| `templates[].id` | string | Template id. |\n| `templates[].name` | string | Template name. |\n| `templates[].text` | string | Message text. |\n| `templates[].created` | integer | Unix timestamp the template was created. |\n| `count` | integer | Number of templates returned. |\n| `next_page` | string | Path to the next page, present only when a full page was returned. |\n| `error` | string | Empty on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid after parameter` | 400 | `after` is not a 24 character hex id. |\n| `Invalid limit parameter` | 400 | `limit` is not numeric. |\n\nAuthentication errors are listed in the intro's 401 table.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK (first page)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"count\": 2,\n  \"templates\": [\n    {\n      \"id\": \"66c4f0a1b2c3d4e5f6a7b8c9\",\n      \"name\": \"Appointment reminder\",\n      \"text\": \"Hi {{FirstName}}, see you at 3pm.\",\n      \"created\": 1755648000\n    },\n    {\n      \"id\": \"66b2e1f0a1b2c3d4e5f6a7b8\",\n      \"name\": \"Payment due\",\n      \"text\": \"Your invoice is due tomorrow.\",\n      \"created\": 1753142400\n    }\n  ],\n  \"next_page\": \"/api/v5/smstemplates?after=66b2e1f0a1b2c3d4e5f6a7b8&limit=2\"\n}"
            },
            {
              "name": "200 OK (last page)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"count\": 1,\n  \"templates\": [\n    {\n      \"id\": \"66a0d0e0f1a2b3c4d5e6f7a8\",\n      \"name\": \"Welcome\",\n      \"text\": \"Thanks for signing up.\",\n      \"created\": 1750464000\n    }\n  ]\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Get SMS Template",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/smstemplates/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "smstemplates",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "66c4f0a1b2c3d4e5f6a7b8c9",
                  "description": "Template id, 24 hex characters."
                }
              ]
            },
            "description": "Fetch one SMS template.\n\n#### Path parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `id` | string | Yes | Template id, 24 hex characters. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `template.id` | string | Template id. |\n| `template.name` | string | Template name. |\n| `template.text` | string | Message text. |\n| `template.created` | integer | Unix timestamp the template was created. |\n| `error` | string | Empty on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid template ID` | 400 | The path segment is not a 24 character hex id. |\n| `Template not found` | 400 | No template with that id exists on this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"template\": {\n    \"id\": \"66c4f0a1b2c3d4e5f6a7b8c9\",\n    \"name\": \"Appointment reminder\",\n    \"text\": \"Hi {{FirstName}}, see you at 3pm.\",\n    \"created\": 1755648000\n  }\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Template not found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Create SMS Template",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/smstemplates",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "smstemplates"
              ]
            },
            "description": "Create a saved SMS template. Templates are text you reuse when sending; Send SMS takes the message text directly, so a template is copied into the send by your own code or picked in the dashboard.\n\nWhen a message goes to a recipient that matches one of your contacts, the send path substitutes `{{FirstName}}` / `{{LastName}}` (and the `[#Tag#]` form) from that contact. Placeholders it cannot resolve are left in the message, so only use them for contact-backed sends.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `name` | string | Yes | Template name, 100 characters or less. Names need not be unique. |\n| `text` | string | Yes | Message text, up to 4096 bytes. Leading and trailing whitespace is trimmed. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `template.id` | string | New template id. |\n| `template.name` | string | Stored name, trimmed. |\n| `template.text` | string | Stored message text, trimmed. |\n| `template.created` | integer | Unix timestamp the template was created. |\n| `error` | string | Empty on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Required parameter: name missing` | 400 | `name` was not sent. |\n| `Required parameter: text missing` | 400 | `text` was not sent. |\n| `Field name must be a string` | 400 | `name` was sent as something other than a string. |\n| `Field text must be a string` | 400 | `text` was sent as something other than a string. |\n| `Field text exceeds the 4096 byte limit` | 400 | Message text too large. |\n| `Template limit reached (100)` | 400 | The account already holds 100 SMS templates. Delete one first. |\n| `Missing name` | 400 | `name` is empty or whitespace only. |\n| `Name must be 100 characters or less` | 400 | Name too long. |\n| `Missing text` | 400 | `text` is empty or whitespace only. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"name\": \"Appointment reminder\",\n  \"text\": \"Hi {{FirstName}}, see you at 3pm.\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"template\": {\n    \"id\": \"66c4f0a1b2c3d4e5f6a7b8c9\",\n    \"name\": \"Appointment reminder\",\n    \"text\": \"Hi {{FirstName}}, see you at 3pm.\",\n    \"created\": 1755648000\n  }\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Template limit reached (100)\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Update SMS Template",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/smstemplates/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "smstemplates",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "66c4f0a1b2c3d4e5f6a7b8c9",
                  "description": "Template id, 24 hex characters."
                }
              ]
            },
            "description": "Update a saved SMS template. Send only the fields you want to change: a field you omit keeps its stored value, and a JSON `null` reads as omitted. Neither field can be set to an empty string, because both are required on an SMS template.\n\n#### Path parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `id` | string | Yes | Template id, 24 hex characters. |\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `name` | string | No | New name, 100 characters or less. |\n| `text` | string | No | New message text, up to 4096 bytes. |\n\n#### Response fields\nSame shape as Create SMS Template, reflecting the stored template after the update.\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid template ID` | 400 | The path segment is not a 24 character hex id. |\n| `Template not found` | 400 | No template with that id exists on this account. |\n| `No fields to update` | 400 | Neither `name` nor `text` was sent. |\n| `Field name must be a string` | 400 | `name` was sent as something other than a string. |\n| `Field text must be a string` | 400 | `text` was sent as something other than a string. |\n| `Field text exceeds the 4096 byte limit` | 400 | Message text too large. |\n| `Missing name` | 400 | `name` was sent empty or whitespace only. |\n| `Name must be 100 characters or less` | 400 | Name too long. |\n| `Missing text` | 400 | `text` was sent empty or whitespace only. |\n| `Template not loaded` | 400 | Internal guard; not reachable through this endpoint. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"text\": \"Hi {{FirstName}}, your appointment moved to 4pm.\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"template\": {\n    \"id\": \"66c4f0a1b2c3d4e5f6a7b8c9\",\n    \"name\": \"Appointment reminder\",\n    \"text\": \"Hi {{FirstName}}, your appointment moved to 4pm.\",\n    \"created\": 1755648000\n  }\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"No fields to update\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Delete SMS Template",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/smstemplates/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "smstemplates",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "66c4f0a1b2c3d4e5f6a7b8c9",
                  "description": "Template id, 24 hex characters."
                }
              ]
            },
            "description": "Delete a saved SMS template. The delete is immediate and permanent. Messages already sent from the template are unaffected, because each send stores its own message text.\n\n#### Path parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `id` | string | Yes | Template id, 24 hex characters. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `message` | string | `Template deleted` on success. |\n| `error` | string | Empty on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid template ID` | 400 | The path segment is not a 24 character hex id. |\n| `Template not found` | 400 | No template with that id exists on this account. |\n| `Template not loaded` | 400 | Internal guard; not reachable through this endpoint. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": \"Template deleted\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Template not found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Conversations",
      "item": [
        {
          "name": "List Conversations",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/conversations?per_page=50",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "conversations"
              ],
              "query": [
                {
                  "key": "before",
                  "value": "",
                  "disabled": true,
                  "description": "Cursor from a previous response's next_cursor. Omit for the first page."
                },
                {
                  "key": "per_page",
                  "value": "50",
                  "description": "Page size, max 200. Default 50."
                },
                {
                  "key": "q",
                  "value": "",
                  "disabled": true,
                  "description": "Digit search on the contact number."
                },
                {
                  "key": "replies",
                  "value": "1",
                  "disabled": true,
                  "description": "Set to 1 to return only conversations that have an inbound reply."
                },
                {
                  "key": "unread",
                  "value": "1",
                  "disabled": true,
                  "description": "Set to 1 to return only unread conversations."
                }
              ]
            },
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            },
            "description": "List the account's two-way SMS conversations, newest activity first, cursor-paginated. Requires Conversations (threads) enabled and at least one virtual mobile number (VMN).\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `conversations` | array | One entry per conversation, newest activity first. |\n| `conversations[].id` | string | Conversation id. |\n| `conversations[].remote_number` | string | Contact number, normalised to `61` international form. |\n| `conversations[].contact_name` | string | Matched contact name, or empty. |\n| `conversations[].last_message_at` | integer | Epoch seconds of the latest message. |\n| `conversations[].unread` | boolean | Unread flag. |\n| `conversations[].has_inbound` | boolean | True once the contact has replied. |\n| `conversations[].last_local_number` | string | The VMN this thread is on. |\n| `conversations[].created_at` | integer | Epoch seconds the conversation began. |\n| `next_cursor` | string or null | Pass as `before` for the next page; null on the last page. |\n| `count` | integer | Conversations on this page. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Conversations are not enabled for this account. Enable Conversations in your 5cSMS dashboard settings, or contact support to turn on two-way messaging.` | 400 | Account lacks the `ENABLE_THREADS` flag. Response also carries `error_code: \"threads_disabled\"`. |\n| `Conversations require a dedicated virtual mobile number (VMN). Add a VMN in your 5cSMS dashboard under Virtual Numbers, then inbound replies will thread automatically.` | 400 | Threads on but the account has no VMN. Response also carries `error_code: \"no_vmn\"`. |\n| `Failed (Method Not Supported)` | 400 | A POST or DELETE was sent to any `/conversations` route. The endpoint is read-only. |\n\n#### Notes\n- Gate failures return an extra `error_code` field (`threads_disabled` or `no_vmn`) so an integrator can branch programmatically.\n- Follow `next_cursor` (passed back as the `before` parameter) until it is null.\n- `q` matches digits in the remote number; `replies=1` and `unread=1` are independent filters.\n- To send, use `POST /api/v5/sms`. Replies thread into the conversation automatically."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"conversations\": [\n    {\n      \"id\": \"66b0a1c2d3e4f5a6b7c8d9e0\",\n      \"remote_number\": \"61412345678\",\n      \"contact_name\": \"Jane Smith\",\n      \"last_message_at\": 1749600120,\n      \"unread\": true,\n      \"has_inbound\": true,\n      \"last_local_number\": \"61480000000\",\n      \"created_at\": 1749000000\n    }\n  ],\n  \"next_cursor\": \"1749600120_66b0a1c2d3e4f5a6b7c8d9e0\",\n  \"count\": 1\n}"
            },
            {
              "name": "200 OK (Last Page)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"conversations\": [\n    {\n      \"id\": \"66a90b1c2d3e4f5a6b7c8d90\",\n      \"remote_number\": \"61412000111\",\n      \"contact_name\": \"\",\n      \"last_message_at\": 1748900000,\n      \"unread\": false,\n      \"has_inbound\": false,\n      \"last_local_number\": \"61480000000\",\n      \"created_at\": 1748900000\n    }\n  ],\n  \"next_cursor\": null,\n  \"count\": 1\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Conversations are not enabled for this account. Enable Conversations in your 5cSMS dashboard settings, or contact support to turn on two-way messaging.\",\n  \"error_code\": \"threads_disabled\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Resolve Conversation by Number",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/conversations?number=0412345678",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "conversations"
              ],
              "query": [
                {
                  "key": "number",
                  "value": "0412345678",
                  "description": "Required. Recipient MSISDN in 04..., +61..., or 61... form; normalised to 61 international form."
                }
              ]
            },
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            },
            "description": "Resolve a single conversation by the remote phone number. The number is normalised to `61` international form, so `04...`, `+61...`, and `61...` inputs all resolve.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `conversation` | object or null | The matching conversation summary (same fields as a List entry, plus `contact_name`), or `null` when the number has no thread. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Conversations are not enabled for this account. Enable Conversations in your 5cSMS dashboard settings, or contact support to turn on two-way messaging.` | 400 | Account lacks the `ENABLE_THREADS` flag. Response also carries `error_code: \"threads_disabled\"`. |\n| `Conversations require a dedicated virtual mobile number (VMN). Add a VMN in your 5cSMS dashboard under Virtual Numbers, then inbound replies will thread automatically.` | 400 | Threads on but the account has no VMN. Response also carries `error_code: \"no_vmn\"`. |\n\n#### Notes\n- A number with no conversation returns `{\"error\": \"\", \"conversation\": null}`. This is a success, not an error.\n- Gate failures return an extra `error_code` field (`threads_disabled` or `no_vmn`)."
          },
          "response": [
            {
              "name": "200 OK (resolved)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"conversation\": {\n    \"id\": \"66b0a1c2d3e4f5a6b7c8d9e0\",\n    \"remote_number\": \"61412345678\",\n    \"contact_name\": \"Jane Smith\",\n    \"last_message_at\": 1749600120,\n    \"unread\": true,\n    \"has_inbound\": true,\n    \"last_local_number\": \"61480000000\",\n    \"created_at\": 1749000000\n  }\n}"
            },
            {
              "name": "200 OK (no thread)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"conversation\": null\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Get Conversation Messages",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/conversations/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "conversations",
                ":id"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50",
                  "disabled": true,
                  "description": "Page size, 1 to 100. Default 50."
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "66b0a1c2d3e4f5a6b7c8d9e0",
                  "description": "Conversation id."
                }
              ]
            },
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            },
            "description": "Fetch the conversation summary plus the most recent page of messages (oldest to newest within the page). Opening a conversation marks it read.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `conversation` | object | Conversation summary, including `contact_name`. |\n| `messages` | array | Messages, oldest to newest within the page. |\n| `messages[].id` | string | Message id. |\n| `messages[].message` | string | Message text. |\n| `messages[].timestamp` | integer | Epoch seconds. |\n| `messages[].type` | string | `MT` (sent) or `MO` (received). |\n| `messages[].status` | integer or null | Delivery status code (MT only). See the message status table in the Introduction. |\n| `messages[].status_text` | string | Human-readable status, e.g. \"Delivered\" (MT only). |\n| `messages[].scheduled` | boolean | Present and true when the message is scheduled (MT only). |\n| `messages[].cancelled` | boolean | Present and true when the message was cancelled (MT only). |\n| `messages[].cancellable` | boolean | Present and true when the message can still be cancelled (MT only). |\n| `messages[].images` | array | Image URLs (MMS only). |\n| `has_more` | boolean | True when older messages remain (page back with `action=older`). |\n| `oldest_mo_ts` | integer | Oldest received-message watermark on this page (use as `before_mo`). |\n| `oldest_mt_ts` | integer | Oldest sent-message watermark on this page (use as `before_mt`). |\n| `newest_mo_ts` | integer | Newest received-message watermark (use as `since_mo` when polling). |\n| `newest_mt_ts` | integer | Newest sent-message watermark (use as `since_mt` when polling). |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Conversations are not enabled for this account. Enable Conversations in your 5cSMS dashboard settings, or contact support to turn on two-way messaging.` | 400 | Account lacks the `ENABLE_THREADS` flag. Response also carries `error_code: \"threads_disabled\"`. |\n| `Conversations require a dedicated virtual mobile number (VMN). Add a VMN in your 5cSMS dashboard under Virtual Numbers, then inbound replies will thread automatically.` | 400 | Threads on but the account has no VMN. Response also carries `error_code: \"no_vmn\"`. |\n| `Failed (Conversation Not Found)` | 400 | The `id` was not found or is not owned by this account. |\n\n#### Notes\n- Side effect: opening a conversation marks it read (`unread` becomes false).\n- Page back through history with the **Page Older Messages** request, and fetch new messages with the **Poll Conversation** request.\n- To send a reply, use `POST /api/v5/sms`. Replies thread into the conversation automatically."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"conversation\": {\n    \"id\": \"66b0a1c2d3e4f5a6b7c8d9e0\",\n    \"remote_number\": \"61412345678\",\n    \"contact_name\": \"Jane Smith\",\n    \"last_message_at\": 1749600120,\n    \"unread\": false,\n    \"has_inbound\": true,\n    \"last_local_number\": \"61480000000\",\n    \"created_at\": 1749000000\n  },\n  \"messages\": [\n    {\n      \"id\": \"66a0a1c2d3e4f5a6b7c8d9e0\",\n      \"message\": \"Your booking is confirmed\",\n      \"timestamp\": 1749600060,\n      \"type\": \"MT\",\n      \"status\": 1002,\n      \"status_text\": \"Sent (Delivery Confirmed)\"\n    },\n    {\n      \"id\": \"66a1a1c2d3e4f5a6b7c8d9e0\",\n      \"message\": \"Yes please\",\n      \"timestamp\": 1749600120,\n      \"type\": \"MO\"\n    }\n  ],\n  \"has_more\": false,\n  \"oldest_mo_ts\": 1749600120,\n  \"oldest_mt_ts\": 1749600060,\n  \"newest_mo_ts\": 1749600120,\n  \"newest_mt_ts\": 1749600060\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Failed (Conversation Not Found)\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Poll Conversation",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/conversations/:id?action=poll&since_mo=1749600120&since_mt=1749600060",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "conversations",
                ":id"
              ],
              "query": [
                {
                  "key": "action",
                  "value": "poll",
                  "description": "Required. Set to poll."
                },
                {
                  "key": "since_mo",
                  "value": "1749600120",
                  "description": "Required. Received-message watermark (epoch seconds); use the prior response's newest_mo_ts."
                },
                {
                  "key": "since_mt",
                  "value": "1749600060",
                  "description": "Required. Sent-message watermark (epoch seconds); use the prior response's newest_mt_ts."
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "66b0a1c2d3e4f5a6b7c8d9e0",
                  "description": "Conversation id."
                }
              ]
            },
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            },
            "description": "Return only messages newer than the supplied watermarks. Use the `newest_mo_ts` and `newest_mt_ts` from the initial load (or the previous poll) as `since_mo` and `since_mt`. A poll that returns new messages marks the conversation read.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `messages` | array | Only messages newer than the watermarks (same message shape as **Get Conversation Messages**). |\n| `now` | integer | Server epoch seconds, for poll cadence. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Conversations are not enabled for this account. Enable Conversations in your 5cSMS dashboard settings, or contact support to turn on two-way messaging.` | 400 | Account lacks the `ENABLE_THREADS` flag. Response also carries `error_code: \"threads_disabled\"`. |\n| `Conversations require a dedicated virtual mobile number (VMN). Add a VMN in your 5cSMS dashboard under Virtual Numbers, then inbound replies will thread automatically.` | 400 | Threads on but the account has no VMN. Response also carries `error_code: \"no_vmn\"`. |\n| `Failed (Conversation Not Found)` | 400 | The `id` was not found or is not owned by this account. |\n| `Failed (Invalid Watermark)` | 400 | `since_mo` or `since_mt` is missing or not a digit string. |\n\n#### Notes\n- Side effect: a poll that returns one or more new messages marks the conversation read.\n- `since_mo` and `since_mt` must be integer epoch-second values."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"messages\": [\n    {\n      \"id\": \"66a2a1c2d3e4f5a6b7c8d9e0\",\n      \"message\": \"On my way\",\n      \"timestamp\": 1749600300,\n      \"type\": \"MO\"\n    }\n  ],\n  \"now\": 1749600305\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Page Older Messages",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/conversations/:id?action=older&before_mo=1749600120&before_mt=1749600060",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "conversations",
                ":id"
              ],
              "query": [
                {
                  "key": "action",
                  "value": "older",
                  "description": "Required. Set to older."
                },
                {
                  "key": "before_mo",
                  "value": "1749600120",
                  "description": "Required. Use the prior response's oldest_mo_ts."
                },
                {
                  "key": "before_mt",
                  "value": "1749600060",
                  "description": "Required. Use the prior response's oldest_mt_ts."
                },
                {
                  "key": "limit",
                  "value": "50",
                  "disabled": true,
                  "description": "Page size, 1 to 100. Default 50."
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "66b0a1c2d3e4f5a6b7c8d9e0",
                  "description": "Conversation id."
                }
              ]
            },
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            },
            "description": "Page back through history older than the supplied cursors. Use the `oldest_mo_ts` and `oldest_mt_ts` from the previous page as `before_mo` and `before_mt`.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `messages` | array | The older page (same message shape as **Get Conversation Messages**). |\n| `has_more` | boolean | True when still-older messages remain. |\n| `oldest_mo_ts` | integer | Oldest received-message watermark on this page (use as the next `before_mo`). |\n| `oldest_mt_ts` | integer | Oldest sent-message watermark on this page (use as the next `before_mt`). |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Conversations are not enabled for this account. Enable Conversations in your 5cSMS dashboard settings, or contact support to turn on two-way messaging.` | 400 | Account lacks the `ENABLE_THREADS` flag. Response also carries `error_code: \"threads_disabled\"`. |\n| `Conversations require a dedicated virtual mobile number (VMN). Add a VMN in your 5cSMS dashboard under Virtual Numbers, then inbound replies will thread automatically.` | 400 | Threads on but the account has no VMN. Response also carries `error_code: \"no_vmn\"`. |\n| `Failed (Conversation Not Found)` | 400 | The `id` was not found or is not owned by this account. |\n| `Failed (Invalid Watermark)` | 400 | `before_mo` or `before_mt` is missing or not a digit string. |\n\n#### Notes\n- This request does not mark the conversation read (unlike the initial load and a poll that returns new messages).\n- Keep paging with the returned `oldest_mo_ts` / `oldest_mt_ts` until `has_more` is `false` — the final page comes back with an empty `messages` array and both watermarks 0."
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"messages\": [\n    {\n      \"id\": \"669f0a1c2d3e4f5a6b7c8d90\",\n      \"message\": \"Earlier message\",\n      \"timestamp\": 1749500050,\n      \"type\": \"MO\"\n    }\n  ],\n  \"has_more\": true,\n  \"oldest_mo_ts\": 1749500050,\n  \"oldest_mt_ts\": 1749500000\n}"
            },
            {
              "name": "200 OK (No Older Messages)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"messages\": [],\n  \"has_more\": false,\n  \"oldest_mo_ts\": 0,\n  \"oldest_mt_ts\": 0\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Email",
      "item": [
        {
          "name": "Send Email",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_dingo}}/email",
              "host": [
                "{{base_url_dingo}}"
              ],
              "path": [
                "email"
              ]
            },
            "description": "Send a transactional email. The sender must be on a registered, validated domain (see Email Domains).\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `SenderEmail` | string | Yes | From address. Must be on a registered, validated domain. |\n| `Subject` | string | Yes | Email subject. |\n| `Text` | string | Conditional | Plain-text body. Required unless `TemplateID` is supplied. |\n| `Html` | string | No | HTML body. Defaults to `Text` if omitted. |\n| `TemplateID` | string | No | Id of a saved email template (see List Email Templates). When present, `Text` and `Html` come from the template and must not be sent in the request. |\n| `TemplateModel` | object | No | Field name to value map used to fill `{{FieldName}}` and `[#FieldName#]` placeholders in `Subject`, `Text` and `Html`. Values must be strings or numbers. |\n| `SenderName` | string | No | Display name for the From address. |\n| `Recipient` | string or array | Conditional | To recipient(s). At least one of Recipient / CC / BCC is required; combined max 50. |\n| `CC` | string or array | Conditional | CC recipient(s). |\n| `BCC` | string or array | Conditional | BCC recipient(s). |\n| `ReplyTo` | string | No | Reply-To address. |\n| `Attachments` | array | No | Array of `{url, filename?}`. Requires the DINGO_ATTACHMENT account flag; max 5. |\n| `test` | boolean | No | Sandbox mode. When true, the request is fully validated (sender authorization, recipient limits, attachments) and a message record is created, but the email is **not** sent and **does not** consume email quota. Assessed as true unless the parameter is omitted or is one of these values: `false`, `\"false\"`, `0`, `\"0\"`. |\n\n**Templates and merge fields:** save an HTML template in the dashboard (Email, then Templates) or with Create Email Template, using placeholders written as `{{FieldName}}` or `[#FieldName#]`, then send it by passing `TemplateID` plus a `TemplateModel` of values. Placeholders are matched literally, so `{{ FieldName }}` with inner spaces will not be filled. Any placeholder your `TemplateModel` does not supply is removed from the message rather than delivered as raw text, and substitution runs against `Subject` as well as the bodies. `TemplateModel` also works on its own with an inline `Text` / `Html` body. Call Get Email Template to see which fields a template references. Substitution only happens when `TemplateID` or `TemplateModel` is present, so a body containing literal braces is untouched otherwise.\n\n**Test mode:** pass `test: true` to validate an email end-to-end without sending or billing. A `v2_emails` record is still created (visible via `GET /api/v5/email` with `Status: test`), but SES is never called.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `messages` | array | One entry per send. |\n| `messages[].Id` | string | Email id. |\n| `messages[].Status` | string | Email status. |\n| `messages[].Recipients` | object | `{To, CC, TotalCount}` — BCC omitted for privacy. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Email not enabled. Please contact us.` | 400 | Email is not enabled on this account. |\n| `Required parameter: {field} missing` | 400 | A required field (SenderEmail / Subject / Text) is missing. |\n| `Required parameter: At least one of Recipient, CC, or BCC must be provided` | 400 | No recipient supplied. |\n| `Too many recipients. Maximum 50 allowed, got {n}.` | 400 | Combined recipients exceed 50. |\n| `Invalid email addresses: {list}` | 400 | One or more recipient addresses are malformed. |\n| `Invalid Sender Email` | 400 | `SenderEmail` is malformed. |\n| `Invalid Sender Domain` | 400 | Sender domain is not registered or validated on this account. |\n| `Invalid Sender Domain or Mailbox` | 400 | For hosted-mailbox senders, the address is not one of the account's active mailboxes. |\n| `Attachments not enabled for this account` | 403 | Attachments require the DINGO_ATTACHMENT flag. |\n| `Maximum {n} attachments allowed` | 400 | Too many attachments (max 5). |\n| `Each attachment must have a url field` | 400 | An attachment entry is missing `url`. |\n| `Provide either TemplateID or Text/Html, not both` | 400 | `TemplateID` was sent together with `Text` or `Html`. |\n| `Invalid TemplateID` | 400 | `TemplateID` is not a 24 character hex id. |\n| `Template not found` | 400 | No template with that id exists on this account. |\n| `Template has no content` | 400 | The template has neither HTML nor plain-text content. |\n| `TemplateModel must be an object of field name to value` | 400 | `TemplateModel` was not sent as an object. |\n| `TemplateModel values must be strings or numbers` | 400 | A `TemplateModel` value is an array, object, or boolean. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"SenderEmail\": \"news@mail.example.com\",\n  \"SenderName\": \"Example\",\n  \"Subject\": \"Welcome {{firstName}}\",\n  \"TemplateID\": \"66c4f0a1b2c3d4e5f6a7b8c9\",\n  \"TemplateModel\": {\n    \"firstName\": \"Jane\",\n    \"orderId\": \"10482\"\n  },\n  \"Recipient\": \"jane@example.com\",\n  \"ReplyTo\": \"support@example.com\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"messages\": [\n    {\n      \"Id\": \"683554c596937cc4b90f5cf7\",\n      \"Status\": \"queued\",\n      \"Recipients\": {\n        \"To\": [\n          \"jane@example.com\"\n        ],\n        \"CC\": [],\n        \"TotalCount\": 1\n      }\n    }\n  ]\n}"
            },
            {
              "name": "200 OK (test mode)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"messages\": [\n    {\n      \"Id\": \"6650f1a2b3c4d5e6f7a8b9c0\",\n      \"Status\": \"test\",\n      \"Recipients\": {\n        \"To\": [\n          \"someone@example.com\"\n        ],\n        \"CC\": [],\n        \"TotalCount\": 1\n      }\n    }\n  ]\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Invalid Sender Domain\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "List Emails",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_dingo}}/email",
              "host": [
                "{{base_url_dingo}}"
              ],
              "path": [
                "email"
              ],
              "query": [
                {
                  "key": "after",
                  "value": "",
                  "description": "24-hex cursor — the `id` of the last email on the previous page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "20",
                  "description": "Page size, 1–100. Default 20.",
                  "disabled": true
                }
              ]
            },
            "description": "List sent emails, newest first.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `emails` | array | Emails this page. |\n| `emails[].id` | string | Email id. |\n| `emails[].status` | string | Email status. |\n| `emails[].from` | string | From address. |\n| `emails[].sender_name` | string | From display name. |\n| `emails[].to` | array | To recipients. |\n| `emails[].cc` | array | CC recipients. |\n| `emails[].subject` | string | Subject. |\n| `emails[].created_at` | integer | Epoch seconds created. |\n| `emails[].method` | string | Send method. |\n| `count` | integer | Emails on this page. |\n| `next_page` | string | Relative path for the next page — present only when `count >= limit`. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid after parameter` | 400 | `after` is not a valid 24-hex cursor. |\n| `Invalid limit parameter` | 400 | `limit` is outside 1–100. |\n\n#### Notes\n- `next_page` appears only when a full page is returned (`count >= limit`); follow it until it is absent. It echoes any non-default `limit`. The paging examples below use `limit=2` to stay short.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"emails\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf7\",\n      \"status\": \"sent\",\n      \"from\": \"news@mail.example.com\",\n      \"sender_name\": \"Example\",\n      \"to\": [\n        \"jane@example.com\"\n      ],\n      \"cc\": [],\n      \"subject\": \"Welcome\",\n      \"created_at\": 1717000000,\n      \"method\": \"api\"\n    },\n    {\n      \"id\": \"683554c596937cc4b90f5cf5\",\n      \"status\": \"sent\",\n      \"from\": \"news@mail.example.com\",\n      \"sender_name\": \"Example\",\n      \"to\": [\n        \"tom@example.com\"\n      ],\n      \"cc\": [],\n      \"subject\": \"Your receipt\",\n      \"created_at\": 1716999000,\n      \"method\": \"api\"\n    }\n  ],\n  \"count\": 2,\n  \"next_page\": \"/api/v5/email?after=683554c596937cc4b90f5cf5&limit=2\"\n}"
            },
            {
              "name": "200 OK (Last Page)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"emails\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf1\",\n      \"status\": \"sent\",\n      \"from\": \"news@mail.example.com\",\n      \"sender_name\": \"Example\",\n      \"to\": [\n        \"amy@example.com\"\n      ],\n      \"cc\": [],\n      \"subject\": \"Order shipped\",\n      \"created_at\": 1716998000,\n      \"method\": \"api\"\n    }\n  ],\n  \"count\": 1\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Get Email",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_dingo}}/email/:id",
              "host": [
                "{{base_url_dingo}}"
              ],
              "path": [
                "email",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Email id."
                }
              ]
            },
            "description": "Fetch a single email, including its full text and HTML bodies.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `email` | object | id, status, from, sender_name, to, cc, subject, text, html, reply_to, created_at, method. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid email ID` | 400 | `id` is not a valid 24-hex id. |\n| `Email not found` | 400 | No such email, or not owned by this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"email\": {\n    \"id\": \"683554c596937cc4b90f5cf7\",\n    \"status\": \"sent\",\n    \"from\": \"news@mail.example.com\",\n    \"sender_name\": \"Example\",\n    \"to\": [\n      \"jane@example.com\"\n    ],\n    \"cc\": [],\n    \"subject\": \"Welcome\",\n    \"text\": \"Hello there\",\n    \"html\": \"<p>Hello there</p>\",\n    \"reply_to\": \"support@example.com\",\n    \"created_at\": 1717000000,\n    \"method\": \"api\"\n  }\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "List Inbound Emails",
          "request": {
            "method": "GET",
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": {
              "raw": "{{base_url_dingo}}/inboundemail",
              "host": [ "{{base_url_dingo}}" ],
              "path": [ "inboundemail" ],
              "query": [
                { "key": "after", "value": "", "description": "24-hex cursor — the `id` of the last inbound email on the previous page.", "disabled": true },
                { "key": "limit", "value": "20", "description": "Page size, 1–1000. Default 20.", "disabled": true },
                { "key": "mailbox_address", "value": "", "description": "Optional. Restrict to inbound routed to this hosted mailbox address (lowercased). Omit for all inbound.", "disabled": true },
                { "key": "include_archived", "value": "1", "description": "Optional. Set to 1 to include archived inbound email. Archived email is excluded by default.", "disabled": true }
              ]
            },
            "description": "List received (inbound) emails for the account, newest first. Returns both hosted-mailbox-routed and custom-domain-routed inbound mail; pass `mailbox_address` to restrict to one hosted mailbox.\n\n#### Query parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `after` | string | No | 24-hex cursor; `id` of the last row on the previous page. |\n| `limit` | integer | No | Page size 1–1000, default 20. |\n| `mailbox_address` | string | No | Restrict to one hosted mailbox address. |\n| `include_archived` | boolean | No | Set to `1` to include archived inbound email. Excluded by default. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `emails` | array | Inbound emails this page, newest first. |\n| `emails[].id` | string | Inbound email id. |\n| `emails[].from` | string | Sender address. |\n| `emails[].from_name` | string | Sender display name, decoded to UTF-8. Empty when the sender supplied none, and on email received before this field shipped. |\n| `emails[].to` | string | Raw To header the mail was delivered to. |\n| `emails[].subject` | string | Subject. |\n| `emails[].has_attachments` | boolean | True if the email has any attachment (inline or file). |\n| `emails[].archived` | boolean | True when the account has archived this email. Only ever `true` when `include_archived` is set. |\n| `emails[].authentication` | object | Sender-authentication outcome for the email. Always present. |\n| `emails[].authentication.available` | boolean | False when the message arrived without a trustworthy authentication assessment; every result then reads `unknown`. |\n| `emails[].authentication.spf.result` | string | One of `pass`, `fail`, `softfail`, `neutral`, `none`, `temperror`, `permerror`, `unknown`. |\n| `emails[].authentication.spf.domain` | string | Envelope-sender domain SPF was evaluated against. |\n| `emails[].authentication.spf.client_ip` | string | IP address the message was delivered from. |\n| `emails[].authentication.spf.helo` | string | HELO/EHLO name the sending host presented. |\n| `emails[].authentication.spf.envelope_from` | string | Envelope sender (return path) address. |\n| `emails[].authentication.dkim.result` | string | Same enum as `spf.result`. When the sender applied more than one signature, this is the result for the first one evaluated; `dkim.signatures` lists them all. |\n| `emails[].authentication.dkim.domain` | string | Signing domain of the DKIM signature this `result` refers to. |\n| `emails[].authentication.dkim.selector` | string | Selector of that signature. Empty when it could not be matched. |\n| `emails[].authentication.dkim.algorithm` | string | `rsa-sha256`, `rsa-sha1` or `ed25519-sha256`. Empty when not recognised. |\n| `emails[].authentication.dkim.signatures` | array | Every DKIM signature the sender applied: `domain`, `selector`, `algorithm`. Empty for unsigned mail. |\n| `emails[].authentication.dmarc.result` | string | Same enum as `spf.result`. |\n| `emails[].authentication.dmarc.from_domain` | string | Domain of the `From` header, which is what DMARC evaluates. |\n| `emails[].authentication.dmarc.alignment.spf` | boolean | True when `spf.domain` matches `dmarc.from_domain` exactly or as a subdomain. |\n| `emails[].authentication.dmarc.alignment.dkim` | boolean | True when `dkim.domain` matches `dmarc.from_domain` exactly or as a subdomain. |\n| `emails[].authentication.hops` | integer | Number of relay hops recorded on the message. |\n| `emails[].mailbox_address` | string | Present only for hosted-mailbox-routed inbound. |\n| `emails[].created_at` | integer | Epoch seconds received. |\n| `count` | integer | Emails on this page. |\n| `next_page` | string | Relative path for the next page — present only when `count >= limit`. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid after parameter` | 400 | `after` is not a valid 24-hex cursor. |\n| `Invalid limit parameter` | 400 | `limit` is non-numeric. |\n| `Invalid mailbox_address parameter` | 400 | `mailbox_address` is not a string. |\n| `Mailbox Not Found` | 400 | `mailbox_address` is not a hosted mailbox on your account. |\n| `Unsupported method` | 400 | The collection route was called with DELETE, or with POST (POST is only valid on `/inboundemail/{id}`). |\n\n#### Notes\n- `next_page` appears only when a full page is returned (`count >= limit`); follow it until it is absent. It echoes any non-default `limit`, the `mailbox_address` filter, and `include_archived`. The paging examples below use `limit=2` to stay short.\n- `mailbox_address` must be one of your own hosted mailboxes. An address that does not exist, or that belongs to another account, returns `Mailbox Not Found` rather than an empty list, so a typo is not mistaken for an empty mailbox. Plus-addressed forms such as `sales+tag@…` are not mailboxes and are rejected for the same reason; filter on the base address. A mailbox you have disabled is still accepted, so its earlier inbound stays listable.\n- Archived inbound email is excluded from this list. Pass `include_archived=1` to include it; `next_page` echoes the flag. Archive with `POST /inboundemail/{id}`. Archiving cannot be reversed.\n- `authentication` reports what sender authentication produced for the message as it arrived. It is reporting only: inbound email is never rejected, quarantined or altered on the basis of these results, and a `fail` on any of the three still appears in this list and is still delivered to your webhook.\n- Alignment is relaxed and suffix-based: `mail.example.com` aligns with `example.com`, but two sibling registrable domains never align. It is not evaluated against the Public Suffix List.\n- `available: false` means no trustworthy assessment accompanied the message. It is the value returned for a message whose only authentication header came from an untrusted source. It is not itself an authentication failure.\n- `spf.domain` and `spf.envelope_from` can both be empty while `spf.result` is populated, which is normal for a message with a null return path such as a bounce.",
            "body": {
              "mode": "raw",
              "options": { "raw": { "language": "json" } },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"emails\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf7\",\n      \"from\": \"jane@example.com\",\n      \"from_name\": \"Jane Smith\",\n      \"to\": \"support@mail.example.com\",\n      \"subject\": \"Re: Welcome\",\n      \"has_attachments\": false,\n      \"archived\": false,\n      \"authentication\": {\n        \"available\": true,\n        \"spf\": { \"result\": \"pass\", \"domain\": \"example.com\", \"client_ip\": \"209.85.214.180\", \"helo\": \"mail-pl1-f180.google.com\", \"envelope_from\": \"jane@example.com\" },\n        \"dkim\": { \"result\": \"pass\", \"domain\": \"example.com\", \"selector\": \"google\", \"algorithm\": \"rsa-sha256\", \"signatures\": [ { \"domain\": \"example.com\", \"selector\": \"google\", \"algorithm\": \"rsa-sha256\" } ] },\n        \"dmarc\": { \"result\": \"pass\", \"from_domain\": \"example.com\", \"alignment\": { \"spf\": true, \"dkim\": true } },\n        \"hops\": 3\n      },\n      \"created_at\": 1717000000\n    },\n    {\n      \"id\": \"683554c596937cc4b90f5cf5\",\n      \"from\": \"tom@example.com\",\n      \"from_name\": \"\",\n      \"to\": \"hello@mail.example.com\",\n      \"subject\": \"Invoice attached\",\n      \"has_attachments\": true,\n      \"archived\": false,\n      \"authentication\": {\n        \"available\": true,\n        \"spf\": { \"result\": \"fail\", \"domain\": \"example.com\", \"client_ip\": \"198.51.100.24\", \"helo\": \"mail.spoofer.example\", \"envelope_from\": \"tom@example.com\" },\n        \"dkim\": { \"result\": \"none\", \"domain\": \"\", \"selector\": \"\", \"algorithm\": \"\", \"signatures\": [] },\n        \"dmarc\": { \"result\": \"fail\", \"from_domain\": \"example.com\", \"alignment\": { \"spf\": true, \"dkim\": false } },\n        \"hops\": 2\n      },\n      \"mailbox_address\": \"hello@mail.example.com\",\n      \"created_at\": 1716999000\n    }\n  ],\n  \"count\": 2,\n  \"next_page\": \"/api/v5/inboundemail?after=683554c596937cc4b90f5cf5&limit=2\"\n}"
            },
            {
              "name": "200 OK (Last Page)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"emails\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf1\",\n      \"from\": \"amy@example.com\",\n      \"from_name\": \"Amy Jones\",\n      \"to\": \"support@mail.example.com\",\n      \"subject\": \"Question about my order\",\n      \"has_attachments\": false,\n      \"archived\": false,\n      \"authentication\": {\n        \"available\": false,\n        \"spf\": { \"result\": \"unknown\", \"domain\": \"\", \"client_ip\": \"\", \"helo\": \"\", \"envelope_from\": \"\" },\n        \"dkim\": { \"result\": \"unknown\", \"domain\": \"\", \"selector\": \"\", \"algorithm\": \"\", \"signatures\": [] },\n        \"dmarc\": { \"result\": \"unknown\", \"from_domain\": \"\", \"alignment\": { \"spf\": false, \"dkim\": false } },\n        \"hops\": 0\n      },\n      \"created_at\": 1716998000\n    }\n  ],\n  \"count\": 1\n}"
            },
            {
              "name": "200 OK (Including Archived)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"emails\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf7\",\n      \"from\": \"jane@example.com\",\n      \"from_name\": \"Jane Smith\",\n      \"to\": \"support@mail.example.com\",\n      \"subject\": \"Re: Welcome\",\n      \"has_attachments\": false,\n      \"archived\": false,\n      \"authentication\": {\n        \"available\": true,\n        \"spf\": { \"result\": \"pass\", \"domain\": \"example.com\", \"client_ip\": \"209.85.214.180\", \"helo\": \"mail-pl1-f180.google.com\", \"envelope_from\": \"jane@example.com\" },\n        \"dkim\": { \"result\": \"pass\", \"domain\": \"example.com\", \"selector\": \"google\", \"algorithm\": \"rsa-sha256\", \"signatures\": [ { \"domain\": \"example.com\", \"selector\": \"google\", \"algorithm\": \"rsa-sha256\" } ] },\n        \"dmarc\": { \"result\": \"pass\", \"from_domain\": \"example.com\", \"alignment\": { \"spf\": true, \"dkim\": true } },\n        \"hops\": 3\n      },\n      \"created_at\": 1717000000\n    },\n    {\n      \"id\": \"683554c596937cc4b90f5cf5\",\n      \"from\": \"tom@example.com\",\n      \"from_name\": \"\",\n      \"to\": \"hello@mail.example.com\",\n      \"subject\": \"Invoice attached\",\n      \"has_attachments\": true,\n      \"archived\": true,\n      \"authentication\": {\n        \"available\": true,\n        \"spf\": { \"result\": \"fail\", \"domain\": \"example.com\", \"client_ip\": \"198.51.100.24\", \"helo\": \"mail.spoofer.example\", \"envelope_from\": \"tom@example.com\" },\n        \"dkim\": { \"result\": \"none\", \"domain\": \"\", \"selector\": \"\", \"algorithm\": \"\", \"signatures\": [] },\n        \"dmarc\": { \"result\": \"fail\", \"from_domain\": \"example.com\", \"alignment\": { \"spf\": true, \"dkim\": false } },\n        \"hops\": 2\n      },\n      \"mailbox_address\": \"hello@mail.example.com\",\n      \"created_at\": 1716999000\n    }\n  ],\n  \"count\": 2,\n  \"next_page\": \"/api/v5/inboundemail?after=683554c596937cc4b90f5cf5&include_archived=1&limit=2\"\n}"
            },
            {
              "name": "400 Bad Request (Unknown Mailbox)",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Mailbox Not Found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Get Inbound Email",
          "request": {
            "method": "GET",
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": {
              "raw": "{{base_url_dingo}}/inboundemail/:id",
              "host": [ "{{base_url_dingo}}" ],
              "path": [ "inboundemail", ":id" ],
              "variable": [
                { "key": "id", "value": "", "description": "Inbound email id." }
              ]
            },
            "description": "Fetch a single received email, including its full text and HTML bodies and attachment metadata. Scoped to the owning account.\n\n#### Path parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `id` | string | Yes | 24-hex inbound email id. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `email.id` | string | Inbound email id. |\n| `email.from` | string | Sender address. |\n| `email.from_name` | string | Sender display name, decoded to UTF-8. Empty when the sender supplied none, and on email received before this field shipped. |\n| `email.to` | string | Raw To header. |\n| `email.subject` | string | Subject. |\n| `email.text` | string | Plain-text body. |\n| `email.html` | string | HTML body (inline `cid:` images already rewritten to S3 URLs). |\n| `email.attachments` | array | Attachment metadata. |\n| `email.attachments[].filename` | string | File name. |\n| `email.attachments[].content_type` | string | MIME type. |\n| `email.attachments[].size` | integer | Bytes. |\n| `email.attachments[].url` | string | Presigned S3 download URL, valid for 1 hour. |\n| `email.attachments[].content_id` | string | MIME Content-ID (inline images). |\n| `email.attachments[].is_inline` | boolean | True for embedded inline images. |\n| `email.mailbox_address` | string | Present only for hosted-mailbox-routed inbound. |\n| `email.mailbox_id` | string | Present only with `mailbox_address`. |\n| `email.archived` | boolean | True when the account has archived this email. Archived email stays fully readable through this endpoint. |\n| `email.archived_at` | integer | Epoch seconds of the archive request. Present only when `archived` is true. |\n| `email.authentication` | object | Sender-authentication outcome for the email. Always present. |\n| `email.authentication.available` | boolean | False when the message arrived without a trustworthy authentication assessment; every result then reads `unknown`. |\n| `email.authentication.spf.result` | string | One of `pass`, `fail`, `softfail`, `neutral`, `none`, `temperror`, `permerror`, `unknown`. |\n| `email.authentication.spf.domain` | string | Envelope-sender domain SPF was evaluated against. |\n| `email.authentication.spf.client_ip` | string | IP address the message was delivered from. |\n| `email.authentication.spf.helo` | string | HELO/EHLO name the sending host presented. |\n| `email.authentication.spf.envelope_from` | string | Envelope sender (return path) address. |\n| `email.authentication.dkim.result` | string | Same enum as `spf.result`. When the sender applied more than one signature, this is the result for the first one evaluated; `dkim.signatures` lists them all. |\n| `email.authentication.dkim.domain` | string | Signing domain of the DKIM signature this `result` refers to. |\n| `email.authentication.dkim.selector` | string | Selector of that signature. Empty when it could not be matched. |\n| `email.authentication.dkim.algorithm` | string | `rsa-sha256`, `rsa-sha1` or `ed25519-sha256`. Empty when not recognised. |\n| `email.authentication.dkim.signatures` | array | Every DKIM signature the sender applied: `domain`, `selector`, `algorithm`. Empty for unsigned mail. |\n| `email.authentication.dmarc.result` | string | Same enum as `spf.result`. |\n| `email.authentication.dmarc.from_domain` | string | Domain of the `From` header, which is what DMARC evaluates. |\n| `email.authentication.dmarc.alignment.spf` | boolean | True when `spf.domain` matches `dmarc.from_domain` exactly or as a subdomain. |\n| `email.authentication.dmarc.alignment.dkim` | boolean | True when `dkim.domain` matches `dmarc.from_domain` exactly or as a subdomain. |\n| `email.authentication.hops` | integer | Number of relay hops recorded on the message. |\n| `email.created_at` | integer | Epoch seconds received. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid inbound email ID` | 400 | `id` is not a valid 24-hex id. |\n| `Inbound email not found` | 400 | No such inbound email, or not owned by this account. |\n| `Unsupported method` | 400 | The route was called with DELETE. POST on this route archives (see Archive Inbound Email). |\n\n#### Notes\n- `authentication` reports what sender authentication produced for the message as it arrived. It is reporting only: inbound email is never rejected, quarantined or altered on the basis of these results.\n- Alignment is relaxed and suffix-based: `mail.example.com` aligns with `example.com`, but two sibling registrable domains never align. It is not evaluated against the Public Suffix List.\n- `available: false` means no trustworthy assessment accompanied the message. It is the value returned for a message whose only authentication header came from an untrusted source. It is not itself an authentication failure.\n- `spf.domain` and `spf.envelope_from` can both be empty while `spf.result` is populated, which is normal for a message with a null return path such as a bounce.",
            "body": {
              "mode": "raw",
              "options": { "raw": { "language": "json" } },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"email\": {\n    \"id\": \"683554c596937cc4b90f5cf7\",\n    \"from\": \"jane@example.com\",\n    \"from_name\": \"Jane Smith\",\n    \"to\": \"support@mail.example.com\",\n    \"subject\": \"Re: Welcome\",\n    \"text\": \"Thanks!\",\n    \"html\": \"<p>Thanks!</p>\",\n    \"attachments\": [],\n    \"archived\": false,\n    \"authentication\": {\n      \"available\": true,\n      \"spf\": { \"result\": \"pass\", \"domain\": \"example.com\", \"client_ip\": \"209.85.214.180\", \"helo\": \"mail-pl1-f180.google.com\", \"envelope_from\": \"jane@example.com\" },\n      \"dkim\": { \"result\": \"pass\", \"domain\": \"example.com\", \"selector\": \"google\", \"algorithm\": \"rsa-sha256\", \"signatures\": [ { \"domain\": \"example.com\", \"selector\": \"google\", \"algorithm\": \"rsa-sha256\" } ] },\n      \"dmarc\": { \"result\": \"pass\", \"from_domain\": \"example.com\", \"alignment\": { \"spf\": true, \"dkim\": true } },\n      \"hops\": 3\n    },\n    \"created_at\": 1717000000\n  }\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Archive Inbound Email",
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" }
            ],
            "url": {
              "raw": "{{base_url_dingo}}/inboundemail/:id",
              "host": [ "{{base_url_dingo}}" ],
              "path": [ "inboundemail", ":id" ],
              "variable": [
                { "key": "id", "value": "", "description": "Inbound email id." }
              ]
            },
            "description": "Archive one received email. Archiving hides the email from `GET /inboundemail` unless that call passes `include_archived=1`; it never deletes the email or its attachments, does not change its one-year retention, and the email stays fully readable through `GET /inboundemail/{id}`.\n\n**Archiving is permanent.** There is no unarchive operation, on this endpoint or in the dashboard. Sending `archived: false` returns `Unarchiving is not supported`.\n\n#### Request body\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `archived` | boolean | Yes | Must be truthy (`true`, `1`, `\"true\"`). A falsy value is rejected - archiving cannot be reversed. |\n\n#### Path parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `id` | string | Yes | 24-hex inbound email id. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `message` | string | `Inbound email archived`. |\n| `id` | string | The inbound email id acted on. |\n| `archived` | boolean | Always `true` on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid inbound email ID` | 400 | `id` is not a valid 24-hex id. |\n| `archived Required. Please see our API Docs` | 400 | The body has no `archived` field. |\n| `Unarchiving is not supported` | 400 | `archived` was sent as `false`, `0`, `\"false\"`, or `null`. |\n| `Inbound email not found` | 400 | No such inbound email, or not owned by this account. |\n| `Unsupported method` | 400 | POSTed to `/inboundemail` with no id. |\n\n#### Notes\n- Idempotent: re-archiving an already-archived email succeeds and returns the same body. It refreshes `archived_at` to the time of the latest request.\n- Archiving is free and does not touch the email quota.",
            "body": {
              "mode": "raw",
              "options": { "raw": { "language": "json" } },
              "raw": "{\n  \"archived\": true\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": \"Inbound email archived\",\n  \"id\": \"683554c596937cc4b90f5cf7\",\n  \"archived\": true\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Unarchiving is not supported\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Email Templates",
      "item": [
        {
          "name": "List Email Templates",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_dingo}}/emailtemplates?limit=2",
              "host": [
                "{{base_url_dingo}}"
              ],
              "path": [
                "emailtemplates"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "2",
                  "description": "Rows per page, 1 to 100. Defaults to 20."
                },
                {
                  "key": "after",
                  "value": null,
                  "disabled": true,
                  "description": "Cursor: id of the last template on the previous page."
                }
              ]
            },
            "description": "List the saved email templates on your account, newest first. Templates can be created and edited in the dashboard (Email, then Templates) or over this API. Pass a template's `id` as `TemplateID` on Send Email.\n\n#### Query parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `limit` | integer | No | Rows per page, 1 to 100. Defaults to 20. |\n| `after` | string | No | Cursor: the `id` of the last template on the previous page. Returns older templates only. |\n\n`next_page` is present only when a full page was returned, so paginate until it is absent. An account can hold at most 100 templates.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `templates` | array | One object per template. |\n| `templates[].id` | string | Template id. |\n| `templates[].name` | string | Template name. |\n| `templates[].created` | integer | Unix timestamp the template was created, or `null` on a legacy document with no stored timestamp. |\n| `count` | integer | Number of templates returned. |\n| `next_page` | string | Path to the next page, present only when a full page was returned. |\n| `error` | string | Empty on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid after parameter` | 400 | `after` is not a 24 character hex id. |\n| `Invalid limit parameter` | 400 | `limit` is not numeric. |\n\nAuthentication errors are listed in the intro's 401 table.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK (first page)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"count\": 2,\n  \"templates\": [\n    {\n      \"id\": \"66c4f0a1b2c3d4e5f6a7b8c9\",\n      \"name\": \"Order confirmation\",\n      \"created\": 1755648000\n    },\n    {\n      \"id\": \"66b2e1f0a1b2c3d4e5f6a7b8\",\n      \"name\": \"Password reset\",\n      \"created\": 1753142400\n    }\n  ],\n  \"next_page\": \"/api/v5/emailtemplates?after=66b2e1f0a1b2c3d4e5f6a7b8&limit=2\"\n}"
            },
            {
              "name": "200 OK (last page)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"count\": 1,\n  \"templates\": [\n    {\n      \"id\": \"66a0d0e0f1a2b3c4d5e6f7a8\",\n      \"name\": \"Welcome\",\n      \"created\": 1750464000\n    }\n  ]\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Get Email Template",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_dingo}}/emailtemplates/:id",
              "host": [
                "{{base_url_dingo}}"
              ],
              "path": [
                "emailtemplates",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "66c4f0a1b2c3d4e5f6a7b8c9",
                  "description": "Template id, 24 hex characters."
                }
              ]
            },
            "description": "Fetch one template with its content, plus the merge fields it references so you know what `TemplateModel` has to supply on Send Email.\n\n#### Path parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `id` | string | Yes | Template id, 24 hex characters. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `template.id` | string | Template id. |\n| `template.name` | string | Template name. |\n| `template.html_content` | string | HTML body stored on the template. Empty string when the template is plain text only. |\n| `template.plain_content` | string | Plain-text body stored on the template. Empty string when the template is HTML only. |\n| `template.created` | integer | Unix timestamp the template was created. |\n| `template.merge_fields` | array | Distinct field names referenced by the template, in first-seen order across the HTML then plain-text content. Supply each as a key of `TemplateModel`. |\n| `error` | string | Empty on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid template ID` | 400 | The path segment is not a 24 character hex id. |\n| `Template not found` | 400 | No template with that id exists on this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"template\": {\n    \"id\": \"66c4f0a1b2c3d4e5f6a7b8c9\",\n    \"name\": \"Order confirmation\",\n    \"html_content\": \"<p>Hi {{firstName}}, order {{orderId}} is on its way.</p>\",\n    \"plain_content\": \"Hi {{firstName}}, order {{orderId}} is on its way.\",\n    \"created\": 1755648000,\n    \"merge_fields\": [\n      \"firstName\",\n      \"orderId\"\n    ]\n  }\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Template not found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Create Email Template",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_dingo}}/emailtemplates",
              "host": [
                "{{base_url_dingo}}"
              ],
              "path": [
                "emailtemplates"
              ]
            },
            "description": "Create a saved email template. Write placeholders as `{{FieldName}}` or `[#FieldName#]` and fill them at send time with Send Email's `TemplateModel`.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `name` | string | Yes | Template name, 100 characters or less. Names need not be unique. |\n| `html_content` | string | Conditional | HTML body. At least one of `html_content` / `plain_content` is required, each limited to 524288 bytes. |\n| `plain_content` | string | Conditional | Plain-text body. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `template.id` | string | New template id. Pass as `TemplateID` on Send Email. |\n| `template.name` | string | Stored name, trimmed. |\n| `template.html_content` | string | Stored HTML body. |\n| `template.plain_content` | string | Stored plain-text body. |\n| `template.created` | integer | Unix timestamp the template was created. |\n| `template.merge_fields` | array | Distinct field names the template references. |\n| `error` | string | Empty on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Required parameter: name missing` | 400 | `name` was not sent. |\n| `Required parameter: html_content or plain_content missing` | 400 | Neither body was sent. |\n| `Field name must be a string` | 400 | `name` was sent as something other than a string. |\n| `Field html_content must be a string` | 400 | `html_content` was sent as something other than a string. |\n| `Field plain_content must be a string` | 400 | `plain_content` was sent as something other than a string. |\n| `Field html_content exceeds the 524288 byte limit` | 400 | HTML body too large. |\n| `Field plain_content exceeds the 524288 byte limit` | 400 | Plain-text body too large. |\n| `Template limit reached (100)` | 400 | The account already holds 100 templates. Delete one first. |\n| `Missing name` | 400 | `name` is empty or whitespace only. |\n| `Name must be 100 characters or less` | 400 | Name too long. |\n| `At least one of html_content or plain_content is required` | 400 | Both bodies are empty strings. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"name\": \"Order confirmation\",\n  \"html_content\": \"<p>Hi {{firstName}}, order {{orderId}} is on its way.</p>\",\n  \"plain_content\": \"Hi {{firstName}}, order {{orderId}} is on its way.\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"template\": {\n    \"id\": \"66c4f0a1b2c3d4e5f6a7b8c9\",\n    \"name\": \"Order confirmation\",\n    \"html_content\": \"<p>Hi {{firstName}}, order {{orderId}} is on its way.</p>\",\n    \"plain_content\": \"Hi {{firstName}}, order {{orderId}} is on its way.\",\n    \"created\": 1755648000,\n    \"merge_fields\": [\n      \"firstName\",\n      \"orderId\"\n    ]\n  }\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Template limit reached (100)\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Update Email Template",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_dingo}}/emailtemplates/:id",
              "host": [
                "{{base_url_dingo}}"
              ],
              "path": [
                "emailtemplates",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "66c4f0a1b2c3d4e5f6a7b8c9",
                  "description": "Template id, 24 hex characters."
                }
              ]
            },
            "description": "Update a saved template. Send only the fields you want to change: a field you omit keeps its stored value. To clear one side of the content, send it as an empty string; a JSON `null` reads as omitted, not as a clear. A template cannot be left with both bodies empty.\n\n#### Path parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `id` | string | Yes | Template id, 24 hex characters. |\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `name` | string | No | New name, 100 characters or less. |\n| `html_content` | string | No | New HTML body, up to 524288 bytes. Empty string clears it. |\n| `plain_content` | string | No | New plain-text body, up to 524288 bytes. Empty string clears it. |\n\n#### Response fields\nSame shape as Create Email Template, reflecting the stored template after the update.\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid template ID` | 400 | The path segment is not a 24 character hex id. |\n| `Template not found` | 400 | No template with that id exists on this account. |\n| `No fields to update` | 400 | None of `name` / `html_content` / `plain_content` was sent. |\n| `Field name must be a string` | 400 | `name` was sent as something other than a string. |\n| `Field html_content must be a string` | 400 | `html_content` was sent as something other than a string. |\n| `Field plain_content must be a string` | 400 | `plain_content` was sent as something other than a string. |\n| `Field html_content exceeds the 524288 byte limit` | 400 | HTML body too large. |\n| `Field plain_content exceeds the 524288 byte limit` | 400 | Plain-text body too large. |\n| `Missing name` | 400 | `name` was sent empty or whitespace only. |\n| `Name must be 100 characters or less` | 400 | Name too long. |\n| `At least one of html_content or plain_content is required` | 400 | The update would leave the template with no body. |\n| `Template not loaded` | 400 | Internal guard; not reachable through this endpoint. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"name\": \"Order confirmation v2\",\n  \"html_content\": \"<p>Hi {{firstName}}, order {{orderId}} has shipped.</p>\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"template\": {\n    \"id\": \"66c4f0a1b2c3d4e5f6a7b8c9\",\n    \"name\": \"Order confirmation v2\",\n    \"html_content\": \"<p>Hi {{firstName}}, order {{orderId}} has shipped.</p>\",\n    \"plain_content\": \"Hi {{firstName}}, order {{orderId}} is on its way.\",\n    \"created\": 1755648000,\n    \"merge_fields\": [\n      \"firstName\",\n      \"orderId\"\n    ]\n  }\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"At least one of html_content or plain_content is required\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Delete Email Template",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_dingo}}/emailtemplates/:id",
              "host": [
                "{{base_url_dingo}}"
              ],
              "path": [
                "emailtemplates",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "66c4f0a1b2c3d4e5f6a7b8c9",
                  "description": "Template id, 24 hex characters."
                }
              ]
            },
            "description": "Delete a saved template. The delete is immediate and permanent. Emails already sent from the template are unaffected, because each send stores its own rendered copy, and a campaign built from the template is unaffected too, because the campaign builder copies the content into the campaign at build time.\n\n#### Path parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `id` | string | Yes | Template id, 24 hex characters. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `message` | string | `Template deleted` on success. |\n| `error` | string | Empty on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid template ID` | 400 | The path segment is not a 24 character hex id. |\n| `Template not found` | 400 | No template with that id exists on this account. |\n| `Template not loaded` | 400 | Internal guard; not reachable through this endpoint. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": \"Template deleted\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Template not found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Account",
      "item": [
        {
          "name": "Get Balance",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/balance",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "balance"
              ]
            },
            "description": "Get your account's credit balance and billing mode.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `balance` | object | Balance details. |\n| `balance.postpaid` | boolean | True if the account is postpaid. |\n| `balance.credits` | number | Available credits. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"balance\": {\n    \"postpaid\": false,\n    \"credits\": 1234.5\n  }\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Get Email Usage",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/emailusage",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "emailusage"
              ]
            },
            "description": "Returns this account's monthly email and attachment-data allowances, how much of each has been consumed so far this month, and when the counters next reset.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `usage.email.quota` | integer | Monthly email allowance. `0` means no email plan. |\n| `usage.email.used` | integer | Emails counted this month. One message counts one unit whether it was sent or received. |\n| `usage.email.percent` | number | `used / quota` as a percentage, one decimal place, capped at 100. `0` when `quota` is 0. |\n| `usage.attachment_data.enabled` | boolean | Whether the account has the attachment feature. Usage is reported either way. |\n| `usage.attachment_data.quota_mb` | integer | Monthly attachment-data allowance in MB. `0` means an allowance of zero, not unlimited. |\n| `usage.attachment_data.used_mb` | number | Attachment bytes transferred this month, in MB to one decimal place. Counts data sent (per recipient) and received. Not the size of stored attachments. |\n| `usage.attachment_data.percent` | number | `used_mb / quota_mb` as a percentage, one decimal place, capped at 100. `100` when `quota_mb` is 0 and any data has been transferred. |\n| `usage.next_renewal` | integer\\|null | Unix timestamp of the next reset, or `null` when nothing resets this account. |\n| `usage.next_renewal_date` | string\\|null | The same instant as `YYYY-MM-DD`, in the platform's timezone, or `null`. This is the same date the account sees on its dashboard. |\n\n#### Notes\n- Both counters are monthly and reset at the renewal. An account with neither a subscription nor postpaid / free-developer billing has no reset, so its figures are cumulative and `next_renewal` is `null`.\n- Quotas are not enforced. Exceeding either one is reported here and blocks nothing.\n- `percent` is provided so clients need not reimplement the zero-quota rule.\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Unsupported method. Please see our API Docs` | 400 | A `POST` or `DELETE` was sent. This endpoint is `GET` only. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"usage\": {\n    \"email\": {\n      \"quota\": 50000,\n      \"used\": 34120,\n      \"percent\": 68.2\n    },\n    \"attachment_data\": {\n      \"enabled\": true,\n      \"quota_mb\": 1024,\n      \"used_mb\": 412.8,\n      \"percent\": 40.3\n    },\n    \"next_renewal\": 1790820000,\n    \"next_renewal_date\": \"2026-10-01\"\n  }\n}"
            },
            {
              "name": "200 OK (no plan, no renewal)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"usage\": {\n    \"email\": {\n      \"quota\": 0,\n      \"used\": 0,\n      \"percent\": 0\n    },\n    \"attachment_data\": {\n      \"enabled\": false,\n      \"quota_mb\": 0,\n      \"used_mb\": 0,\n      \"percent\": 0\n    },\n    \"next_renewal\": null,\n    \"next_renewal_date\": null\n  }\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Pre-warm Send Queue",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/prewarm",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "prewarm"
              ]
            },
            "description": "Pre-warm the send queue ahead of a large campaign. Requires the QUEUE_PREWARM account flag; usable once per 60 minutes.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Account does not have queue pre-warming enabled` | 400 | QUEUE_PREWARM flag not set on the account. |\n| `Queue was already pre-warmed recently. Please wait {n} minutes before trying again.` | 400 | Pre-warmed within the last 60 minutes. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": \"Queue pre-warming initiated.\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Account does not have queue pre-warming enabled\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Logins",
      "item": [
        {
          "name": "Create Login",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/logins",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "logins"
              ]
            },
            "description": "Create (or re-invite) a sub-login on your account.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `name` | string | Yes | Login's display name. |\n| `email` | string | Yes | Login's email address. |\n| `role` | string | No | One of `admin`, `manager`, `sender`, `reporting`. Default `admin`. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `login_id` | string | The login's id. |\n| `is_new` | boolean | True if a new login was created. |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Missing required field: name` | 400 | `name` not supplied. |\n| `Missing required field: email` | 400 | `email` not supplied. |\n| `Invalid email address` | 400 | `email` is malformed. |\n| `Invalid role. Must be one of: admin, manager, sender, reporting` | 400 | `role` is not an allowed value. |\n| `Login already exists on this account` | 400 | A login with that email already exists here. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"name\": \"Jane Smith\",\n  \"email\": \"jane@example.com\",\n  \"role\": \"admin\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"login_id\": \"683554c596937cc4b90f5cf7\",\n  \"is_new\": true,\n  \"message\": \"Login created\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Invalid role. Must be one of: admin, manager, sender, reporting\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "List Logins",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/logins",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "logins"
              ]
            },
            "description": "List all logins on your account.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `logins` | array | Logins on the account. |\n| `logins[].login_id` | string | Login id. |\n| `logins[].name` | string | Display name. |\n| `logins[].email` | string | Email address. |\n| `logins[].role` | string | Role. |\n| `count` | integer | Number of logins. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"logins\": [\n    {\n      \"login_id\": \"683554c596937cc4b90f5cf7\",\n      \"name\": \"Jane Smith\",\n      \"email\": \"jane@example.com\",\n      \"role\": \"admin\"\n    }\n  ],\n  \"count\": 1\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Change Login Role",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/logins/:login_id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "logins",
                ":login_id"
              ],
              "variable": [
                {
                  "key": "login_id",
                  "value": "",
                  "description": "Login id."
                }
              ]
            },
            "description": "Change a login's role.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `action` | string | Yes | Must be `change_role`. |\n| `role` | string | Yes | One of `admin`, `manager`, `sender`, `reporting`. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `login_id` | string | The login's id. |\n| `role` | string | The new role. |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid login_id format` | 400 | `login_id` is not a valid id. |\n| `Missing required field: role` | 400 | `role` not supplied. |\n| `Invalid role. Must be one of: admin, manager, sender, reporting` | 400 | `role` is not an allowed value. |\n| `Login not found on this account` | 400 | No such login on this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"action\": \"change_role\",\n  \"role\": \"manager\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"login_id\": \"683554c596937cc4b90f5cf7\",\n  \"role\": \"manager\",\n  \"message\": \"Role updated\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Delete Login",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/logins/:login_id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "logins",
                ":login_id"
              ],
              "variable": [
                {
                  "key": "login_id",
                  "value": "",
                  "description": "Login id."
                }
              ]
            },
            "description": "Remove a login from your account.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `login_id` | string | The removed login's id. |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid login_id format` | 400 | `login_id` is not a valid id. |\n| `Login not found on this account` | 400 | No such login on this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"login_id\": \"683554c596937cc4b90f5cf7\",\n  \"message\": \"Login removed from account\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Campaigns",
      "item": [
        {
          "name": "Create campaign",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns"
              ]
            },
            "description": "Create an SMS, email, or combined campaign. A campaign is created in `DRAFT` status. In a single call you can also set the campaign's content, add recipients inline, and send or schedule it — or do each step separately with the other endpoints in this folder.\n\nCampaigns are an account resource on the 5c SMS host (`{{base_url_sms}}`) for both channels.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `type` | string | No | `sms`, `email`, or `both`. Default `sms`. |\n| `title` | string | No | Campaign title. |\n| `sms_sender_id` | string | No | SMS sender ID or virtual number (SMS / both). |\n| `sms_body` | string | No | SMS message body (SMS / both). |\n| `sms_optout_enabled` | boolean | No | Append an opt-out link to the SMS. |\n| `sms_unicode_enabled` | boolean | No | Send the SMS as unicode. Requires the `UTF16` account feature. |\n| `sender_email` | string | No | From address (email / both). Validated when stored: it must be one of your active hosted mailboxes, or an address at one of your validated sending domains. Pass an empty string to clear. |\n| `sender_name` | string | No | From name (email / both). |\n| `subject` | string | No | Email subject line (email / both). |\n| `email_body` | string | No | Email HTML body (email / both). |\n| `email_text` | string | No | Email plain-text body (email / both). |\n| `email_template_id` | string | No | 24-char hex id of one of your email templates. Copies the template's HTML and plain-text content into `email_body` / `email_text`. Cannot be combined with `email_body` or `email_text` in the same request. |\n| `sms_template_id` | string | No | 24-char hex id of one of your SMS templates. Copies the template's text into `sms_body`. Cannot be combined with `sms_body` in the same request. |\n| `attachment_urls` | array | No | Email attachments, each an object `{\"url\": \"...\", \"filename\": \"...\"}` (`url` required, `filename` optional). Requires the attachments account feature. Maximum 5. Pass `[]` to clear. See the attachment note below. |\n| `numbers` | string or array | No | Phone numbers to add as individual recipients. Array, or a comma/newline-delimited string. Max 100 individual recipients per call (numbers + emails combined). |\n| `emails` | string or array | No | Email addresses to add as individual recipients. Same format and cap as `numbers`. |\n| `contact_list_ids` | string or array | No | Contact list ids to add as recipient sources. Each must be a 24-char hex id you own. |\n| `send` | boolean | No | If true, send the campaign immediately after it is built. |\n| `schedule` | integer or string | No | Unix timestamp or a `strtotime`-parseable string. Schedules the send for that time; supplying it implies `send`. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. Carries the send error if an inline `send`/`schedule` failed its gating (the campaign is still created). |\n| `campaign_id` | string | The new campaign's id. Returned even when `error` reports a settings failure, so you can retry or delete the draft. |\n| `status` | string | `DRAFT`, or `SENDING` / `SCHEDULED` if sent inline. |\n| `recipient_count` | integer | Recipients currently on the campaign. |\n| `recipients_added` | integer | Individual recipients added (present only when inline recipients were supplied). |\n| `recipient_errors` | array | Per-recipient errors for skipped individuals (present only when inline recipients were supplied). |\n| `send` | object | The send result (present only when `send`/`schedule` was requested): `error`, `campaign_id`, `status`, `message`. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid campaign type` | 400 | `type` is not `sms`, `email`, or `both`. |\n| `Unicode requires the UTF16 feature on your account` | 400 | `sms_unicode_enabled` set without the `UTF16` account feature. |\n| `Provide either email_template_id or email_body/email_text, not both` | 400 | `email_template_id` supplied alongside a literal email body. |\n| `Invalid email_template_id format` | 400 | `email_template_id` is not a 24-char hex id. |\n| `Email template not found` | 400 | No such email template on this account. |\n| `Provide either sms_template_id or sms_body, not both` | 400 | `sms_template_id` supplied alongside `sms_body`. |\n| `Invalid sms_template_id format` | 400 | `sms_template_id` is not a 24-char hex id. |\n| `SMS template not found` | 400 | No such SMS template on this account. |\n| `Template has no content` | 400 | The referenced template's body is empty. |\n| `attachment_urls must be an array` | 400 | `attachment_urls` was not an array. |\n| `Attachments not enabled for this account` | 400 | Non-empty `attachment_urls` without the attachments account feature. |\n| `Maximum 5 attachments allowed` | 400 | More than 5 entries in `attachment_urls`. |\n| `Each attachment must have a url field` | 400 | An `attachment_urls` entry is not an object carrying a non-empty `url`. |\n| `Sender email is not a valid email address.` | 400 | `sender_email` is not a parseable address. |\n| `Sender email must be one of your active mailboxes.` | 400 | `sender_email` is on the hosted mailbox domain but is not an active mailbox you own. |\n| `Sender email must use one of your validated domains.` | 400 | `sender_email`'s domain is not a validated sending domain on this account. |\n| `No recipients provided` | 400 | `numbers`/`emails`/`contact_list_ids` supplied but all empty. |\n| `You need an active email plan to send email campaigns` | 400 | Inline send on an email campaign without email quota. |\n| `You need a verified domain to send email campaigns` | 400 | Inline send on an email campaign with no validated domain. |\n| `You need SMS credits or a postpaid account to send SMS campaigns` | 400 | Inline send on an SMS campaign with no balance. |\n| `No recipients added` | 400 | Inline send with no recipients on the campaign. |\n| `Sender email is required` / `Sender name is required` / `Subject line is required` / `Email body is required` | 400 | Inline send of an incomplete email campaign. |\n| `SMS sender ID is required` / `SMS message body is required` | 400 | Inline send of an incomplete SMS campaign. |\n| `Unicode SMS message is too long (max 335 characters)` | 400 | Inline send; unicode body over the limit. |\n| `SMS message is too long (max 765 characters)` | 400 | Inline send; GSM body over the limit. |\n| `Scheduled time must be in the future` | 400 | `schedule` resolves to a past time. |\n| `Failed to start campaign` | 400 | The send could not be queued. |\n\n> **Note — inline send is not atomic.** When you pass `send`/`schedule` and the send gating fails (see the **Send / schedule campaign** endpoint for those errors), the campaign persists as `DRAFT`, the top-level `error` carries the send failure, and `send` holds the detail. Fix the issue and call the Send endpoint — do not re-create.\n\n> **Note — attachments are fetched once, when the campaign starts.** Each URL in `attachment_urls` is downloaded a single time as the campaign begins sending, stored, and attached to every recipient's email from our storage — your server is not contacted once per recipient. The URL must be publicly reachable at that moment. If any download fails, a single file exceeds 10 MB, or the files total more than 25 MB, the whole campaign stops before any message is sent, returns to `DRAFT`, and the reason appears in the campaign's `send_error` field (see **Get campaign**). Attachments uploaded in the dashboard count toward the same 5-file and 25 MB limits.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"type\": \"sms\",\n  \"title\": \"July promo\",\n  \"sms_sender_id\": \"EXAMPLE\",\n  \"sms_body\": \"Hello from Acme!\",\n  \"sms_optout_enabled\": true,\n  \"contact_list_ids\": [\"683554c596937cc4b90f5cf7\"],\n  \"send\": false\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"campaign_id\": \"665f1a2b3c4d5e6f7a8b9c0d\",\n  \"status\": \"DRAFT\",\n  \"recipient_count\": 240,\n  \"recipients_added\": 0,\n  \"recipient_errors\": []\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Invalid campaign type\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "List campaigns",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns?limit=200",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns"
              ],
              "query": [
                {
                  "key": "after",
                  "value": "",
                  "disabled": true,
                  "description": "Cursor: the campaign_id of the last campaign on the previous page. Omit for the first page."
                },
                {
                  "key": "limit",
                  "value": "200",
                  "disabled": true,
                  "description": "Page size, 1-200. Default 200."
                }
              ]
            },
            "description": "List your account's campaigns, most-recent-first, with keyset pagination. Campaigns are purged 365 days after creation.\n\n#### Query parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `after` | string | No | Cursor: the `campaign_id` of the last campaign on the previous page. Omit for the first page. |\n| `limit` | integer | No | Page size, 1–200. Default 200. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `campaigns` | array | Campaigns, newest first. |\n| `campaigns[].campaign_id` | string | Campaign id. |\n| `campaigns[].title` | string | Campaign title. |\n| `campaigns[].status` | string | `DRAFT`, `SCHEDULED`, `SENDING`, or `SENT`. |\n| `campaigns[].type` | array | Channels: any of `sms`, `email`. |\n| `campaigns[].recipient_count` | integer | Recipients on the campaign. |\n| `campaigns[].schedule` | integer | Scheduled send time (unix), or null. |\n| `count` | integer | Number of campaigns returned. |\n| `next_page` | string | Root-relative path to the next page — `/api/v5/campaigns?after=<last_id>`, plus `&limit=` when non-default. **Present only when a full page was returned** (`count` equals `limit`); absent on the last page. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid after parameter` | 400 | `after` is not a 24-char hex id. |\n| `Invalid limit parameter` | 400 | `limit` is not numeric. |\n\n> **Paging.** Follow `next_page` until it is absent. Because it appears only on a full page, a final page holding exactly `limit` rows costs one extra request that comes back empty — that is expected.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK (first page)",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "OK",
              "code": 200,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"\",\n  \"campaigns\": [\n    {\n      \"campaign_id\": \"665f1a2b3c4d5e6f7a8b9c0d\",\n      \"title\": \"July promo\",\n      \"status\": \"SENT\",\n      \"type\": [\n        \"sms\"\n      ],\n      \"recipient_count\": 240,\n      \"schedule\": null\n    },\n    {\n      \"campaign_id\": \"665f1a2b3c4d5e6f7a8b9c07\",\n      \"title\": \"June newsletter\",\n      \"status\": \"SENT\",\n      \"type\": [\n        \"email\"\n      ],\n      \"recipient_count\": 1820,\n      \"schedule\": null\n    }\n  ],\n  \"count\": 2,\n  \"next_page\": \"/api/v5/campaigns?after=665f1a2b3c4d5e6f7a8b9c07&limit=2\"\n}"
            },
            {
              "name": "200 OK (last page)",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "OK",
              "code": 200,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"\",\n  \"campaigns\": [\n    {\n      \"campaign_id\": \"665f1a2b3c4d5e6f7a8b9c01\",\n      \"title\": \"May promo\",\n      \"status\": \"SENT\",\n      \"type\": [\n        \"sms\"\n      ],\n      \"recipient_count\": 96,\n      \"schedule\": null\n    }\n  ],\n  \"count\": 1\n}"
            },
            {
              "name": "400 Bad Request",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "Bad Request",
              "code": 400,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"Invalid after parameter\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "Unauthorized",
              "code": 401,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Get campaign",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Campaign id (24-char hex)."
                }
              ]
            },
            "description": "Fetch one campaign: its settings, the first page of its recipients, and combined SMS/email delivery stats.\n\nFor a lightweight stats-only read use **Get campaign stats**. To page through every recipient use **Get campaign recipients**.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `campaign_id` | string | Campaign id. |\n| `title` | string | Campaign title. |\n| `status` | string | `DRAFT`, `SCHEDULED`, `SENDING`, or `SENT`. |\n| `type` | array | Channels: any of `sms`, `email`. |\n| `recipient_count` | integer | Recipients on the campaign. |\n| `schedule` | integer | Scheduled send time (unix), or null. |\n| `send_error` | string | Why the last send attempt aborted, or null. Set when a campaign is returned to `DRAFT` because its attachments could not be prepared; cleared automatically on the next send. |\n| `sms` | object | SMS settings (present for `sms`/`both`): `sender_id`, `body`, `optout_enabled`, `unicode_enabled`. |\n| `email` | object | Email settings (present for `email`/`both`): `sender_email`, `sender_name`, `subject`, `has_body`, `attachment_urls`. |\n| `recipients` | array | **The first 100 recipient rows**, newest first. Each row: `id`, `type` (`individual`/`contact_list`), and either `recipient_type`+`value` or `contact_list_id`+`contact_list_name`+`contact_list_count`, plus `created`. |\n| `recipients_next_page` | string | Root-relative path into **Get campaign recipients** for the rows beyond the first 100. Present only when 100 rows were returned. |\n| `stats` | object | Delivery stats: `type` (`sms`/`email`/`both`/`none`); `sms` and/or `email` count objects. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid campaign_id format` | 400 | The path id is not a 24-char hex id. |\n| `Campaign not found` | 400 | No such campaign on this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK (SMS campaign)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"campaign_id\": \"665f1a2b3c4d5e6f7a8b9c0d\",\n  \"title\": \"July promo\",\n  \"status\": \"SENT\",\n  \"type\": [\n    \"sms\"\n  ],\n  \"recipient_count\": 240,\n  \"schedule\": null,\n  \"send_error\": null,\n  \"sms\": {\n    \"sender_id\": \"EXAMPLE\",\n    \"body\": \"Hello {{FirstName}}, from Acme!\",\n    \"optout_enabled\": true,\n    \"unicode_enabled\": false\n  },\n  \"recipients\": [\n    {\n      \"id\": \"665f1a2b3c4d5e6f7a8b9c11\",\n      \"type\": \"contact_list\",\n      \"created\": 1719792000,\n      \"contact_list_id\": \"665f1a2b3c4d5e6f7a8b9c10\",\n      \"contact_list_name\": \"Newsletter\",\n      \"contact_list_count\": 240\n    }\n  ],\n  \"stats\": {\n    \"type\": \"sms\",\n    \"sms\": {\n      \"total\": 240,\n      \"queued\": 0,\n      \"sent\": 12,\n      \"delivered\": 226,\n      \"failed\": 2\n    }\n  }\n}"
            },
            {
              "name": "200 OK (email campaign, recipients paginated)",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "OK",
              "code": 200,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"\",\n  \"campaign_id\": \"665f1a2b3c4d5e6f7a8b9c1f\",\n  \"title\": \"August newsletter\",\n  \"status\": \"SENDING\",\n  \"type\": [\n    \"email\"\n  ],\n  \"recipient_count\": 1820,\n  \"schedule\": null,\n  \"send_error\": null,\n  \"email\": {\n    \"sender_email\": \"news@mail.example.com\",\n    \"sender_name\": \"Example\",\n    \"subject\": \"Hello {{FirstName}}\",\n    \"has_body\": true,\n    \"attachment_urls\": [\n      {\n        \"url\": \"https://example.com/august.pdf\",\n        \"filename\": \"august.pdf\"\n      }\n    ]\n  },\n  \"recipients\": [\n    {\n      \"id\": \"665f1a2b3c4d5e6f7a8b9c90\",\n      \"type\": \"individual\",\n      \"created\": 1719792100,\n      \"recipient_type\": \"email\",\n      \"value\": \"person0@example.com\"\n    },\n    {\n      \"id\": \"665f1a2b3c4d5e6f7a8b9c8f\",\n      \"type\": \"individual\",\n      \"created\": 1719792099,\n      \"recipient_type\": \"email\",\n      \"value\": \"person1@example.com\"\n    },\n    \"\\u2026 98 more rows \\u2026\"\n  ],\n  \"recipients_next_page\": \"/api/v5/campaigns/665f1a2b3c4d5e6f7a8b9c1f/recipients?after=665f1a2b3c4d5e6f7a8b9c2d\",\n  \"stats\": {\n    \"type\": \"email\",\n    \"email\": {\n      \"total\": 1820,\n      \"queued\": 400,\n      \"sent\": 1420,\n      \"delivered\": 1390,\n      \"bounced\": 22,\n      \"failed\": 8\n    }\n  }\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Campaign not found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Update campaign",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Campaign id (24-char hex)."
                }
              ]
            },
            "description": "Update a draft campaign's settings. Only campaigns in `DRAFT` status can be edited. Supply `action: update` plus any of the content fields; only the fields you send are changed.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `action` | string | Yes | Must be `update`. |\n| `type` | string | No | `sms`, `email`, or `both`. |\n| `title` | string | No | Campaign title. |\n| `sms_sender_id` | string | No | SMS sender ID or virtual number. |\n| `sms_body` | string | No | SMS message body. |\n| `sms_optout_enabled` | boolean | No | Append an opt-out link to the SMS. |\n| `sms_unicode_enabled` | boolean | No | Send as unicode. Requires the `UTF16` account feature. |\n| `sender_email` | string | No | From address. Validated when stored: it must be one of your active hosted mailboxes, or an address at one of your validated sending domains. Pass an empty string to clear. |\n| `sender_name` | string | No | From name. |\n| `subject` | string | No | Email subject line. |\n| `email_body` | string | No | Email HTML body. |\n| `email_text` | string | No | Email plain-text body. |\n| `email_template_id` | string | No | 24-char hex id of one of your email templates. Copies the template's HTML and plain-text content into `email_body` / `email_text`. Cannot be combined with `email_body` or `email_text` in the same request. |\n| `sms_template_id` | string | No | 24-char hex id of one of your SMS templates. Copies the template's text into `sms_body`. Cannot be combined with `sms_body` in the same request. |\n| `attachment_urls` | array | No | Email attachments, each an object `{\"url\": \"...\", \"filename\": \"...\"}` (`url` required, `filename` optional). Requires the attachments account feature. Maximum 5. Pass `[]` to clear. See the attachment note below. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `campaign_id` | string | Campaign id. |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid campaign_id format` | 400 | The path id is not a 24-char hex id. |\n| `Campaign not found` | 400 | No such campaign on this account. |\n| `Invalid or missing action` | 400 | `action` is absent or not a supported action. |\n| `Cannot edit campaign - not in draft status` | 400 | The campaign is not in `DRAFT`. |\n| `Invalid campaign type` | 400 | `type` is not `sms`, `email`, or `both`. |\n| `Unicode requires the UTF16 feature on your account` | 400 | `sms_unicode_enabled` set without the `UTF16` account feature. |\n| `Provide either email_template_id or email_body/email_text, not both` | 400 | `email_template_id` supplied alongside a literal email body. |\n| `Invalid email_template_id format` | 400 | `email_template_id` is not a 24-char hex id. |\n| `Email template not found` | 400 | No such email template on this account. |\n| `Provide either sms_template_id or sms_body, not both` | 400 | `sms_template_id` supplied alongside `sms_body`. |\n| `Invalid sms_template_id format` | 400 | `sms_template_id` is not a 24-char hex id. |\n| `SMS template not found` | 400 | No such SMS template on this account. |\n| `Template has no content` | 400 | The referenced template's body is empty. |\n| `attachment_urls must be an array` | 400 | `attachment_urls` was not an array. |\n| `Attachments not enabled for this account` | 400 | Non-empty `attachment_urls` without the attachments account feature. |\n| `Maximum 5 attachments allowed` | 400 | More than 5 entries in `attachment_urls`. |\n| `Each attachment must have a url field` | 400 | An `attachment_urls` entry is not an object carrying a non-empty `url`. |\n| `Sender email is not a valid email address.` | 400 | `sender_email` is not a parseable address. |\n| `Sender email must be one of your active mailboxes.` | 400 | `sender_email` is on the hosted mailbox domain but is not an active mailbox you own. |\n| `Sender email must use one of your validated domains.` | 400 | `sender_email`'s domain is not a validated sending domain on this account. |\n\n> **Note — attachments are fetched once, when the campaign starts.** Each URL in `attachment_urls` is downloaded a single time as the campaign begins sending, stored, and attached to every recipient's email from our storage — your server is not contacted once per recipient. The URL must be publicly reachable at that moment. If any download fails, a single file exceeds 10 MB, or the files total more than 25 MB, the whole campaign stops before any message is sent, returns to `DRAFT`, and the reason appears in the campaign's `send_error` field (see **Get campaign**). Attachments uploaded in the dashboard count toward the same 5-file and 25 MB limits.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"action\": \"update\",\n  \"title\": \"July promo (final)\",\n  \"sms_body\": \"Hello from Acme! Reply STOP to opt out.\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"campaign_id\": \"665f1a2b3c4d5e6f7a8b9c0d\",\n  \"message\": \"Campaign updated\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Cannot edit campaign - not in draft status\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Add recipients",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Campaign id (24-char hex)."
                }
              ]
            },
            "description": "Add recipients to a draft campaign — individual numbers, individual emails, and/or whole contact lists. Only campaigns in `DRAFT` status accept recipients. Individual numbers and emails are capped at 100 combined per call; contact lists have no per-call cap. Duplicates and invalid entries are skipped and reported in `errors` while the rest are added (the top-level `error` stays empty).\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `action` | string | Yes | Must be `add_recipients`. |\n| `numbers` | string or array | No | Phone numbers. Array, or comma/newline-delimited string. |\n| `emails` | string or array | No | Email addresses. Same format as `numbers`. |\n| `contact_list_ids` | string or array | No | Contact list ids (24-char hex) you own. |\n\nAt least one of `numbers`, `emails`, or `contact_list_ids` must be non-empty.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `added` | integer | Individual recipients successfully added. |\n| `errors` | array | Per-item messages for skipped entries — e.g. `Number already added: ...`, `Invalid email address: ...`, `Invalid contact list id: ...`, or `Maximum 100 individual recipients per call`. |\n| `recipient_count` | integer | Total recipients on the campaign after the call. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid campaign_id format` | 400 | The path id is not a 24-char hex id. |\n| `Campaign not found` | 400 | No such campaign on this account. |\n| `Invalid or missing action` | 400 | `action` is absent or not a supported action. |\n| `Cannot modify recipients - not in draft status` | 400 | The campaign is not in `DRAFT`. |\n| `No recipients provided` | 400 | None of `numbers`/`emails`/`contact_list_ids` supplied. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"action\": \"add_recipients\",\n  \"numbers\": [\"0404123123\", \"0404123124\"],\n  \"contact_list_ids\": [\"683554c596937cc4b90f5cf7\"]\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"added\": 2,\n  \"errors\": [],\n  \"recipient_count\": 242\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"No recipients provided\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Remove recipient",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Campaign id (24-char hex)."
                }
              ]
            },
            "description": "Remove a single recipient row (an individual, or a whole contact-list source) from a draft campaign. Use the recipient `id` from **Get campaign**'s `recipients` array. Only campaigns in `DRAFT` status can be modified.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `action` | string | Yes | Must be `remove_recipient`. |\n| `recipient_id` | string | Yes | The recipient row id (24-char hex). |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `recipient_count` | integer | Total recipients remaining. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid campaign_id format` | 400 | The path id is not a 24-char hex id. |\n| `Campaign not found` | 400 | No such campaign on this account. |\n| `Invalid or missing action` | 400 | `action` is absent or not a supported action. |\n| `Cannot modify recipients - not in draft status` | 400 | The campaign is not in `DRAFT`. |\n| `recipient_id is required` | 400 | `recipient_id` missing or not a valid id. |\n| `Recipient not found` | 400 | No such recipient row on this campaign. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"action\": \"remove_recipient\",\n  \"recipient_id\": \"665f1a2b3c4d5e6f7a8b9c11\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"recipient_count\": 2\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"recipient_id is required\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Clear recipients",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Campaign id (24-char hex)."
                }
              ]
            },
            "description": "Remove all recipients from a draft campaign. Only campaigns in `DRAFT` status can be modified.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `action` | string | Yes | Must be `clear_recipients`. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `recipient_count` | integer | Always `0` on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid campaign_id format` | 400 | The path id is not a 24-char hex id. |\n| `Campaign not found` | 400 | No such campaign on this account. |\n| `Invalid or missing action` | 400 | `action` is absent or not a supported action. |\n| `Cannot modify recipients - not in draft status` | 400 | The campaign is not in `DRAFT`. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"action\": \"clear_recipients\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"recipient_count\": 0\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Send / schedule campaign",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Campaign id (24-char hex)."
                }
              ]
            },
            "description": "Send a draft campaign now, or schedule it for a future time. The campaign must be in `DRAFT` and pass all readiness and account-gating checks. Omit `schedule` to send immediately; supply it to schedule.\n\nSend-time gating enforces, per channel: an active email plan and a verified sending domain (email), and SMS credits or a postpaid account (SMS). A coupon may only be attached to a Unified Virtual Number sender.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `action` | string | Yes | Must be `send`. |\n| `schedule` | integer or string | No | Unix timestamp or a `strtotime`-parseable string, in the future. Omit to send immediately. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `campaign_id` | string | Campaign id. |\n| `status` | string | `SENDING` (immediate) or `SCHEDULED`. |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid campaign_id format` | 400 | The path id is not a 24-char hex id. |\n| `Campaign not found` | 400 | No such campaign on this account. |\n| `Invalid or missing action` | 400 | `action` is absent or not a supported action. |\n| `Campaign is not in draft status` | 400 | The campaign is not in `DRAFT`. |\n| `You need an active email plan to send email campaigns` | 400 | Email campaign with no email quota. |\n| `You need a verified domain to send email campaigns` | 400 | Email campaign with no verified sending domain. |\n| `You need SMS credits or a postpaid account to send SMS campaigns` | 400 | SMS campaign with zero balance and not postpaid. |\n| `A coupon can only be attached when the Sender ID is a Unified Virtual Number. Remove the coupon or choose a unified sender.` | 400 | Coupon attached with a non-Blue sender. |\n| `Scheduled time must be in the future` | 400 | `schedule` did not parse or is not in the future. |\n| `No recipients added` | 400 | Readiness: campaign has no recipients. |\n| `SMS sender ID is required` | 400 | Readiness: SMS channel missing sender ID. |\n| `SMS message body is required` | 400 | Readiness: SMS channel missing body. |\n| `SMS message is too long (max 765 characters)` | 400 | Readiness: SMS body exceeds the GSM length limit. |\n| `Unicode SMS message is too long (max 335 characters)` | 400 | Readiness: unicode SMS body exceeds the unicode length limit. |\n| `Sender email is required` | 400 | Readiness: email channel missing sender email. |\n| `Sender name is required` | 400 | Readiness: email channel missing sender name. |\n| `Subject line is required` | 400 | Readiness: email channel missing subject. |\n| `Email body is required` | 400 | Readiness: email channel missing body. |\n| `Campaign is already sending` | 400 | Send already in progress. |\n| `Campaign is already scheduled` | 400 | A schedule is already set. |\n| `Campaign has already been sent` | 400 | The campaign was already sent. |\n| `Failed to start campaign` | 400 | The send could not be started (defensive fallback). |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"action\": \"send\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"campaign_id\": \"665f1a2b3c4d5e6f7a8b9c0d\",\n  \"status\": \"SENDING\",\n  \"message\": \"Campaign started\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"You need a verified domain to send email campaigns\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Cancel schedule",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Campaign id (24-char hex)."
                }
              ]
            },
            "description": "Cancel a scheduled campaign and return it to `DRAFT`. Only works while the campaign is `SCHEDULED` and more than 30 minutes remain before the scheduled send.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `action` | string | Yes | Must be `cancel_schedule`. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `campaign_id` | string | Campaign id. |\n| `status` | string | `DRAFT`. |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid campaign_id format` | 400 | The path id is not a 24-char hex id. |\n| `Campaign not found` | 400 | No such campaign on this account. |\n| `Invalid or missing action` | 400 | `action` is absent or not a supported action. |\n| `Campaign is not scheduled` | 400 | The campaign is not in `SCHEDULED`. |\n| `Cannot cancel - less than 30 minutes until scheduled send` | 400 | Too close to the scheduled send time. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"action\": \"cancel_schedule\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"campaign_id\": \"665f1a2b3c4d5e6f7a8b9c0d\",\n  \"status\": \"DRAFT\",\n  \"message\": \"Schedule cancelled\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Cannot cancel - less than 30 minutes until scheduled send\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Delete campaign",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Campaign id (24-char hex)."
                }
              ]
            },
            "description": "Delete a campaign and its recipients. Only draft campaigns can be deleted — campaigns that are sending, scheduled, or already sent cannot be removed.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `campaign_id` | string | The deleted campaign's id. |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid campaign_id format` | 400 | The path id is not a 24-char hex id. |\n| `Campaign not found` | 400 | No such campaign on this account. |\n| `Cannot delete campaign` | 400 | The campaign is sending, scheduled, or sent. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"campaign_id\": \"665f1a2b3c4d5e6f7a8b9c0d\",\n  \"message\": \"Campaign deleted\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Cannot delete campaign\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Check campaign readiness",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Campaign id (24-char hex)."
                }
              ]
            },
            "description": "Ask whether a draft campaign is ready to send, without sending it. This runs the same check the Send endpoint runs internally, so you can surface missing fields in your own UI before committing.\n\nRead-only: it never changes the campaign.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `action` | string | Yes | Must be `check_readiness`. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `ready` | boolean | True when the campaign passes every content and recipient check. |\n| `message` | string | Why it is not ready; an empty string when `ready` is true. |\n| `recipient_count` | integer | Recipients currently on the campaign. |\n\nPossible `message` values: `Campaign is not in draft status`, `No recipients added`, `Sender email is required`, `Sender name is required`, `Subject line is required`, `Email body is required`, `SMS sender ID is required`, `SMS message body is required`, `Unicode SMS message is too long (max 335 characters)`, `SMS message is too long (max 765 characters)`, or one of the sender-email validation messages.\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid campaign_id format` | 400 | The path id is not a 24-char hex id. |\n| `Campaign not found` | 400 | No such campaign on this account. |\n| `Invalid or missing action` | 400 | `action` is absent or not a supported action. |\n| `Invalid request path` | 400 | More than one path segment after `/campaigns`. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"action\": \"check_readiness\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "OK",
              "code": 200,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"\",\n  \"ready\": false,\n  \"message\": \"Sender email is required\",\n  \"recipient_count\": 240\n}"
            },
            {
              "name": "400 Bad Request",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "Bad Request",
              "code": 400,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"Campaign not found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "Unauthorized",
              "code": 401,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Duplicate campaign",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Campaign id (24-char hex)."
                }
              ]
            },
            "description": "Copy a campaign into a new `DRAFT`. Works on a campaign in any status, so this is the normal way to re-run a campaign that has already sent.\n\nCopied: title (prefixed `Copy of `), channels, all email and SMS content, attachments (both uploaded files and `attachment_urls`), and every recipient — individual addresses and numbers as well as contact lists. Not copied: the schedule, send history, unified media, and coupons.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `action` | string | Yes | Must be `duplicate`. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `campaign_id` | string | The **new** campaign's id. |\n| `title` | string | The new campaign's title. |\n| `status` | string | Always `DRAFT`. |\n| `recipient_count` | integer | Recipients copied onto the new campaign. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid campaign_id format` | 400 | The path id is not a 24-char hex id. |\n| `Campaign not found` | 400 | No such campaign on this account. |\n| `Invalid or missing action` | 400 | `action` is absent or not a supported action. |\n| `Invalid request path` | 400 | More than one path segment after `/campaigns`. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"action\": \"duplicate\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "OK",
              "code": 200,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"\",\n  \"campaign_id\": \"665f1a2b3c4d5e6f7a8b9c2e\",\n  \"title\": \"Copy of July promo\",\n  \"status\": \"DRAFT\",\n  \"recipient_count\": 240\n}"
            },
            {
              "name": "400 Bad Request",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "Bad Request",
              "code": 400,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"Campaign not found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "Unauthorized",
              "code": 401,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Get campaign stats",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns/:id/stats",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns",
                ":id",
                "stats"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Campaign id (24-char hex)."
                }
              ]
            },
            "description": "Delivery statistics for one campaign, without its recipient list. Use this to poll a sending campaign — **Get campaign** returns the same `stats` object but also embeds up to 100 recipient rows, so this is the cheaper poll.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `campaign_id` | string | Campaign id. |\n| `status` | string | `DRAFT`, `SCHEDULED`, `SENDING`, or `SENT`. |\n| `stats.type` | string | `sms`, `email`, `both`, or `none`. |\n| `stats.email.total` | integer | Emails generated for this campaign. |\n| `stats.email.queued` | integer | Accepted, not yet sent. |\n| `stats.email.sent` | integer | Handed to the mail provider. |\n| `stats.email.delivered` | integer | Confirmed delivered. |\n| `stats.email.bounced` | integer | Bounced. |\n| `stats.email.failed` | integer | Failed, blocked, or complained. |\n| `stats.sms.total` | integer | Messages generated for this campaign. |\n| `stats.sms.queued` | integer | Queued, scheduled, or held for fraud review. |\n| `stats.sms.sent` | integer | Handed to a carrier. |\n| `stats.sms.delivered` | integer | Confirmed delivered. |\n| `stats.sms.failed` | integer | Failed. |\n| `stats.has_fraud_hold` | boolean | At least one message is held for fraud review. |\n| `stats.has_email_enquiry` | boolean | At least one email is held pending an enquiry. |\n\nThe `email` and `sms` objects are present only for the campaign's own channels.\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid campaign_id format` | 400 | The path id is not a 24-char hex id. |\n| `Campaign not found` | 400 | No such campaign on this account. |\n| `Invalid request path` | 400 | Unrecognised segment after the campaign id. |"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "OK",
              "code": 200,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"\",\n  \"campaign_id\": \"665f1a2b3c4d5e6f7a8b9c0d\",\n  \"status\": \"SENT\",\n  \"stats\": {\n    \"type\": \"sms\",\n    \"sms\": {\n      \"total\": 240,\n      \"queued\": 0,\n      \"sent\": 12,\n      \"delivered\": 226,\n      \"failed\": 2\n    },\n    \"has_fraud_hold\": false,\n    \"has_email_enquiry\": false\n  }\n}"
            },
            {
              "name": "400 Bad Request",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "Bad Request",
              "code": 400,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"Campaign not found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "Unauthorized",
              "code": 401,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Get campaign recipients",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns/:id/recipients?after=&limit=100",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns",
                ":id",
                "recipients"
              ],
              "query": [
                {
                  "key": "after",
                  "value": "",
                  "disabled": true,
                  "description": "Cursor: the id of the last recipient on the previous page."
                },
                {
                  "key": "limit",
                  "value": "100",
                  "disabled": true,
                  "description": "Page size, 1-1000. Default 100."
                }
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Campaign id (24-char hex)."
                }
              ]
            },
            "description": "Page through every recipient on a campaign, newest first. **Get campaign** embeds only the first 100; this endpoint returns all of them.\n\nA recipient row is either an individual address/number or a whole contact list attached as a single row. A contact-list row therefore contributes `contact_list_count` to `recipient_count` while occupying one row here.\n\n#### Query parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `after` | string | No | Cursor: the `id` of the last recipient on the previous page. Omit for the first page. |\n| `limit` | integer | No | Page size, 1–1000. Default 100. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `campaign_id` | string | Campaign id. |\n| `count` | integer | Rows on this page. |\n| `recipient_count` | integer | Total recipients the campaign will send to, counting each contact list's members. |\n| `recipients[].id` | string | Recipient row id — pass as `after` to page, or to **Remove recipient**. |\n| `recipients[].type` | string | `individual` or `contact_list`. |\n| `recipients[].created` | integer | When the row was added (unix). |\n| `recipients[].recipient_type` | string | `number` or `email` (individual rows only). |\n| `recipients[].value` | string | The address or number (individual rows only). |\n| `recipients[].contact_list_id` | string | Contact list id (contact-list rows only). |\n| `recipients[].contact_list_name` | string | Contact list name (contact-list rows only). |\n| `recipients[].contact_list_count` | integer | Members in the list when it was attached (contact-list rows only). |\n| `next_page` | string | Root-relative path to the next page. **Present only when a full page was returned**; absent on the last page. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid campaign_id format` | 400 | The path id is not a 24-char hex id. |\n| `Campaign not found` | 400 | No such campaign on this account. |\n| `Invalid after parameter` | 400 | `after` is not a 24-char hex id. |\n| `Invalid limit parameter` | 400 | `limit` is not numeric. |\n| `Invalid request path` | 400 | Unrecognised segment after the campaign id. |"
          },
          "response": [
            {
              "name": "200 OK (first page)",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "OK",
              "code": 200,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"\",\n  \"campaign_id\": \"665f1a2b3c4d5e6f7a8b9c0d\",\n  \"count\": 2,\n  \"recipient_count\": 242,\n  \"recipients\": [\n    {\n      \"id\": \"665f1a2b3c4d5e6f7a8b9c12\",\n      \"type\": \"individual\",\n      \"created\": 1719792100,\n      \"recipient_type\": \"email\",\n      \"value\": \"sam@example.com\"\n    },\n    {\n      \"id\": \"665f1a2b3c4d5e6f7a8b9c11\",\n      \"type\": \"contact_list\",\n      \"created\": 1719792000,\n      \"contact_list_id\": \"665f1a2b3c4d5e6f7a8b9c10\",\n      \"contact_list_name\": \"Newsletter\",\n      \"contact_list_count\": 240\n    }\n  ],\n  \"next_page\": \"/api/v5/campaigns/665f1a2b3c4d5e6f7a8b9c0d/recipients?after=665f1a2b3c4d5e6f7a8b9c11&limit=2\"\n}"
            },
            {
              "name": "200 OK (last page)",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "OK",
              "code": 200,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"\",\n  \"campaign_id\": \"665f1a2b3c4d5e6f7a8b9c0d\",\n  \"count\": 1,\n  \"recipient_count\": 242,\n  \"recipients\": [\n    {\n      \"id\": \"665f1a2b3c4d5e6f7a8b9c09\",\n      \"type\": \"individual\",\n      \"created\": 1719791900,\n      \"recipient_type\": \"number\",\n      \"value\": \"61412345678\"\n    }\n  ]\n}"
            },
            {
              "name": "400 Bad Request",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "Bad Request",
              "code": 400,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"Invalid after parameter\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "Unauthorized",
              "code": 401,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Get campaign merge fields",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/campaigns/:id/mergefields",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "campaigns",
                ":id",
                "mergefields"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Campaign id (24-char hex)."
                }
              ]
            },
            "description": "List the merge fields available to this campaign's content, derived from the contact lists attached to it.\n\nUse a field in `subject`, `email_body`, `email_text` or `sms_body` as `{{FieldName}}` or `[#FieldName#]`. Each recipient's own value is substituted at send time, and a placeholder with no value for that contact is removed.\n\n> **Merge fields only fill for contact-list recipients.** Individually-added addresses and numbers carry no contact record, so every placeholder is stripped for them. The list is empty until at least one contact list is attached.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `fields` | array | Field names: `FirstName` and `LastName`, then each custom field found on the attached lists. Empty when no contact list is attached. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid campaign_id format` | 400 | The path id is not a 24-char hex id. |\n| `Campaign not found` | 400 | No such campaign on this account. |\n| `Invalid request path` | 400 | Unrecognised segment after the campaign id. |"
          },
          "response": [
            {
              "name": "200 OK",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "OK",
              "code": 200,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"\",\n  \"fields\": [\n    \"FirstName\",\n    \"LastName\",\n    \"Company\",\n    \"RenewalDate\"\n  ]\n}"
            },
            {
              "name": "400 Bad Request",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "Bad Request",
              "code": 400,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"Campaign not found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "originalRequest": {
                "method": "GET",
                "url": ""
              },
              "status": "Unauthorized",
              "code": 401,
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Sender IDs",
      "item": [
        {
          "name": "List Sender IDs",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/senderid",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "senderid"
              ]
            },
            "description": "List the sender IDs registered on your account, plus any a linked master account has chosen to share with you.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `senderids` | array | Registered sender IDs, and any shared with this account by a linked master account. |\n| `senderids[].id` | string | Sender ID record id. |\n| `senderids[].senderid` | string | The sender ID value. |\n| `senderids[].status` | string | Approval status: `pending`, `acma_pending`, `approved`, `acma_approved`, `disallowed`, or `unverified`. |\n| `senderids[].shared` | boolean | `true` when this Sender ID belongs to a linked master account and was shared with you. Shared Sender IDs can be used to send but cannot be edited or deleted through this account's API key. |\n| `senderids[].acma_details_id` | string | Id of the linked ACMA contact-details record. Pass it as `acma_details_id` on **Create Sender ID** to reuse the same details. Omitted when none is linked. |\n| `senderids[].acma_additional_details` | string | Free-text notes supplied for ACMA registration. Omitted when empty. |\n| `senderids[].acma_evidence` | object | Stored supporting evidence: `filename`, `content_type`, `size` (bytes) and `uploaded_at` (Unix timestamp). Omitted until the file has been stored, so it is also how you confirm an `acma_evidence_url` download completed. |\n\nThe three `acma_*` fields describe an ACMA registration, which belongs to the account that owns the sender ID — they are omitted entirely on rows where `shared` is `true`.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"senderids\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf7\",\n      \"senderid\": \"EXAMPLE\",\n      \"status\": \"approved\",\n      \"shared\": false\n    },\n    {\n      \"id\": \"683554c596937cc4b90f5cf8\",\n      \"senderid\": \"MASTERBRAND\",\n      \"status\": \"acma_approved\",\n      \"shared\": true\n    },\n    {\n      \"id\": \"68a1f0c596937cc4b90f5d1a\",\n      \"senderid\": \"ACMEPTY\",\n      \"status\": \"acma_pending\",\n      \"shared\": false,\n      \"acma_details_id\": \"68a1f0c596937cc4b90f5d02\",\n      \"acma_additional_details\": \"ACMEPTY is our registered trading name.\",\n      \"acma_evidence\": {\n        \"filename\": \"authorisation-letter.pdf\",\n        \"content_type\": \"application/pdf\",\n        \"size\": 184320,\n        \"uploaded_at\": 1755043200\n      }\n    }\n  ]\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Create Sender ID",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/senderid",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "senderid"
              ]
            },
            "description": "Register a new sender ID for approval.\n\nText sender IDs must be registered with ACMA before they can send, so the registration material can be supplied with this call. Send the authorised contact details either as `acma_details` (the details themselves — we create the record and return its id) or as `acma_details_id` (an id from **Create ACMA Contact Details**, **List ACMA Contact Details**, or an earlier call to this endpoint) — one or the other, not both. `acma_additional_details` carries supporting information for the registration, and `acma_evidence_url` points at a supporting-evidence file — an authorisation letter, a trade-mark certificate, or whatever backs your claim to the sender ID — that we download and store against it.\n\nEvery ACMA and evidence field applies to text sender IDs only, and all are rejected when the sender ID already exists on the account — use the dashboard to change an existing one.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `senderid` | string | Yes | The sender ID to register. Letters and digits only; max 11 characters. |\n| `acma_details` | object | No | Authorised contact details to create and link. Same fields as **Create ACMA Contact Details**: `contact_name`, `contact_email` and `business_name` are required within the object, `contact_phone`, `abn`, `business_web`, `business_address`, `business_phone` optional. Text sender IDs only. Cannot be combined with `acma_details_id`. |\n| `acma_details_id` | string | No | Id of an ACMA contact-details set already on this account, to link instead of creating one. Text sender IDs only. Cannot be combined with `acma_details`. |\n| `acma_additional_details` | string | No | Supporting information for the ACMA registration. Max 2000 characters. Text sender IDs only. |\n| `acma_evidence_url` | string | No | Public `http`/`https` URL of your supporting evidence (authorisation letter, trade-mark certificate, registration document). We check the URL's shape while handling the request and download it shortly afterwards in the background: 10-second timeout, up to 3 redirects, max 2 MB, must be a PDF, JPG, PNG, DOC or DOCX. Text sender IDs only. |\n| `acma_evidence_filename` | string | No | Filename to store the evidence under. Defaults to the last path segment of `acma_evidence_url`. Its extension decides whether the file type is accepted. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `message` | string | Confirmation message. |\n| `id` | string | Sender ID record id. Returned for text sender IDs. |\n| `acma_details_id` | string | Id of the contact-details set created or linked. Reuse it on your next sender ID. Present only when `acma_details` or `acma_details_id` was supplied. |\n\n#### Notes\n- Registration is not immediate: a new text sender ID is created `pending` and cannot send until it is reviewed, approved and registered with ACMA. This may take several business days. Poll **List Sender IDs** for the status.\n- Supplying `acma_details`, `acma_additional_details` or `acma_evidence_url` notifies our support team so the registration can be progressed.\n- Contact-details sets are reusable across sender IDs. Pass `acma_details` once, then reuse the returned `acma_details_id`, rather than sending the same details again.\n- Nothing is written until every field validates, so a rejected request leaves no sender ID and no contact-details record.\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Missing Sender ID` | 400 | `senderid` not supplied, or not a string. |\n| `Invalid new Sender ID` | 400 | `senderid` is not 1-11 letters and digits. |\n| `ACMA registration details are only supported for text Sender IDs` | 400 | `acma_details`, `acma_details_id`, `acma_additional_details` or `acma_evidence_url` supplied with a numeric sender ID. |\n| `You have already registered this Sender ID with ACMA and its contact details cannot be changed. Resubmit excluding contact details. Contact support for assistance.` | 400 | Registration material supplied for a sender ID this account has already registered with ACMA. The contact details ACMA holds are permanent; resend the request without the `acma_*` fields to recreate the sender ID with the details it is already registered against. |\n| `Sender ID already exists on this account` | 400 | Registration material supplied for a sender ID already on the account. |\n| `Supply either acma_details or acma_details_id, not both` | 400 | Both were supplied. |\n| `Invalid ACMA contact details` | 400 | `acma_details` is not an object, or one of its fields is not a string. |\n| `Contact Name, Contact Email and Business Name are required.` | 400 | `acma_details` is missing one of the three required fields. |\n| `Invalid ACMA details ID` | 400 | `acma_details_id` is not a 24-character hexadecimal id. |\n| `ACMA details not found` | 400 | `acma_details_id` does not belong to this account. |\n| `Invalid Additional Details` | 400 | `acma_additional_details` is not a string. |\n| `Additional Details is too long. Maximum 2000 characters.` | 400 | `acma_additional_details` exceeds 2000 characters. |\n| `Invalid supporting evidence URL` | 400 | `acma_evidence_url` is not a string. |\n| `Supporting evidence URL is not a valid URL.` | 400 | `acma_evidence_url` is malformed. |\n| `Supporting evidence URL must be an http or https URL.` | 400 | `acma_evidence_url` uses another scheme, or has no host. |\n| `Supporting evidence must be a PDF, JPG, PNG, DOC or DOCX file.` | 400 | The filename extension is not an accepted type. |\n| `Sender ID not found.` | 400 | The sender ID was removed while this request was in flight. |\n| `Unable to queue the supporting evidence` | 400 | The sender ID and its details were saved, but the evidence download could not be scheduled. Upload the file in the dashboard; do not retry the create. |\n| `Unable to notify support for this Sender ID` | 400 | The sender ID and its details were saved, but the support notification could not be queued. Do not retry the create; contact support. |\n\nDownload problems — an unreachable URL, a file over 2 MB, a link that has expired — are **not** returned here, because the download happens after this response. We retry once and then email your account contact.\n",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"senderid\": \"EXAMPLE\",\n  \"acma_details\": {\n    \"contact_name\": \"Jane Citizen\",\n    \"contact_email\": \"jane@example.com\",\n    \"contact_phone\": \"0400000000\",\n    \"abn\": \"12345678901\",\n    \"business_name\": \"Example Pty Ltd\",\n    \"business_web\": \"https://www.example.com\",\n    \"business_address\": \"1 Example Street, Sydney NSW 2000\",\n    \"business_phone\": \"0290000000\"\n  },\n  \"acma_additional_details\": \"EXAMPLE is our registered trading name.\",\n  \"acma_evidence_url\": \"https://files.example.com/authorisation-letter.pdf\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": \"Sender ID created\",\n  \"id\": \"683554c596937cc4b90f5cf7\",\n  \"acma_details_id\": \"68a1f0c596937cc4b90f5d02\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Supporting evidence must be a PDF, JPG, PNG, DOC or DOCX file.\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Delete Sender ID",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/senderid/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "senderid",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Sender ID record id."
                }
              ]
            },
            "description": "Remove a sender ID from your account.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid Sender ID format` | 400 | The `id` path segment is not a 24-character hexadecimal id. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Create ACMA Contact Details",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/acmadetails",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "acmadetails"
              ]
            },
            "description": "Create a set of ACMA authorised contact details on your account. ACMA requires these details for every text sender ID; the `id` returned here is what **Create Sender ID** takes as `acma_details_id`, and what **List Sender IDs** reports as `senderids[].acma_details_id`.\n\nSets are per-account and reusable — create one and link it to as many sender IDs as you like. **Create Sender ID** can also create a set inline from raw `acma_details`, which is the one-call alternative to this endpoint. Changing and deleting sets is done in the dashboard under Settings > Sender IDs.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `contact_name` | string | Yes | Name of the authorised representative. |\n| `contact_email` | string | Yes | Email of the authorised representative. ACMA may email this address to confirm the registration. Stored lowercased. |\n| `business_name` | string | Yes | Registered business or organisation name. |\n| `contact_phone` | string | No | Phone number of the authorised representative. |\n| `abn` | string | No | ABN of the entity. Omit for non-ABN entities such as sole traders or individuals. |\n| `business_web` | string | No | Business website. |\n| `business_address` | string | No | Registered business address. |\n| `business_phone` | string | No | Business phone number. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `message` | string | Confirmation message. |\n| `id` | string | Contact-details record id. Pass this as `acma_details_id` on **Create Sender ID**. |\n\n#### Notes\n- Duplicate sets are allowed: each call creates a new record.\n- A set cannot be changed or removed once a sender ID linked to it has been submitted to or registered with ACMA.\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Contact Name, Contact Email and Business Name are required.` | 400 | One of the three required fields is missing or empty. |\n| `Invalid ACMA contact details` | 400 | A supplied field is not a string. |\n| `Unable to notify support for these contact details` | 400 | The details were saved, but the support notification could not be queued. Do not retry; contact support — find the set's id with **List ACMA Contact Details**. |\n| `Unsupported method. Please see our API Docs` | 400 | `DELETE` on this path. Contact-details sets are changed and removed in the dashboard, not over the API. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"contact_name\": \"Jane Citizen\",\n  \"contact_email\": \"jane@example.com\",\n  \"contact_phone\": \"0400000000\",\n  \"abn\": \"12345678901\",\n  \"business_name\": \"Example Pty Ltd\",\n  \"business_web\": \"https://www.example.com\",\n  \"business_address\": \"1 Example Street, Sydney NSW 2000\",\n  \"business_phone\": \"0290000000\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": \"ACMA contact details created\",\n  \"id\": \"68a1f0c596937cc4b90f5d02\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Contact Name, Contact Email and Business Name are required.\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "List ACMA Contact Details",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/acmadetails",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "acmadetails"
              ]
            },
            "description": "List the ACMA authorised contact-details sets on your account, oldest first. Use it to recover an `acma_details_id` you did not keep, or to check which details a sender ID is registered against.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `count` | integer | Number of sets returned. |\n| `acma_details` | array | The account's contact-details sets. |\n| `acma_details[].id` | string | Record id. Pass as `acma_details_id` on **Create Sender ID**. |\n| `acma_details[].contact_name` | string | Authorised representative's name. |\n| `acma_details[].contact_email` | string | Authorised representative's email, lowercased. |\n| `acma_details[].contact_phone` | string | Authorised representative's phone. Empty when not supplied. |\n| `acma_details[].abn` | string | ABN. Empty for non-ABN entities. |\n| `acma_details[].business_name` | string | Registered business or organisation name. |\n| `acma_details[].business_web` | string | Business website. Empty when not supplied. |\n| `acma_details[].business_address` | string | Registered business address. Empty when not supplied. |\n| `acma_details[].business_phone` | string | Business phone. Empty when not supplied. |\n| `acma_details[].created` | integer | Unix timestamp (seconds) the set was created. `null` on sets that predate this field. |\n\n#### Notes\n- An account with no sets returns `count: 0` and an empty array, not an error.\n- To see which sender ID uses which set, read `senderids[].acma_details_id` from **List Sender IDs**.\n\n#### Errors\nNone beyond authentication.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"count\": 1,\n  \"acma_details\": [\n    {\n      \"id\": \"68a1f0c596937cc4b90f5d02\",\n      \"contact_name\": \"Jane Citizen\",\n      \"contact_email\": \"jane@example.com\",\n      \"contact_phone\": \"0400000000\",\n      \"abn\": \"12345678901\",\n      \"business_name\": \"Example Pty Ltd\",\n      \"business_web\": \"https://www.example.com\",\n      \"business_address\": \"1 Example Street, Sydney NSW 2000\",\n      \"business_phone\": \"0290000000\",\n      \"created\": 1755043200\n    }\n  ]\n}"
            },
            {
              "name": "200 OK (no sets)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"count\": 0,\n  \"acma_details\": []\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Virtual Numbers",
      "item": [
        {
          "name": "List Virtual Numbers",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/virtualnumber",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "virtualnumber"
              ]
            },
            "description": "List the virtual numbers on your account.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `virtualnumbers` | array | Virtual numbers. |\n| `virtualnumbers[].id` | string | Virtual number record id. |\n| `virtualnumbers[].number` | string | Number in international format. |\n| `virtualnumbers[].number_formatted` | string | Human-formatted number. |\n| `virtualnumbers[].country` | string | Country code. |\n| `virtualnumbers[].type` | string | `standard` or `unified`. |\n| `count` | integer | Number of virtual numbers. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"virtualnumbers\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf7\",\n      \"number\": \"61400000000\",\n      \"number_formatted\": \"0400 000 000\",\n      \"country\": \"AU\",\n      \"type\": \"standard\"\n    }\n  ],\n  \"count\": 1\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Purchase Virtual Number",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/virtualnumber",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "virtualnumber"
              ]
            },
            "description": "Purchase a virtual number. Charges the card on file and creates a monthly subscription. Default limit 2 numbers unless the MORE_VMNS flag is set.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `country` | string | Yes | Only `AU` is supported for API purchases. |\n| `type` | string | No | `standard` or `unified`. Default `standard`; `unified` requires Unified Messaging to be enabled for the account and provisions a Unified Virtual Number, which `GET /virtualnumber` reports as `type: unified`. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `message` | string | Confirmation message. |\n| `virtualnumber` | object | `{id, number, number_formatted, country}`. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Missing required parameter: country` | 400 | `country` not supplied. |\n| `Invalid country. Currently only AU is supported for API requests.` | 400 | `country` is not `AU`. |\n| `Invalid type. Must be \"standard\" or \"unified\".` | 400 | `type` is not an allowed value. |\n| `Your account is not enabled for Unified Mobile Numbers. Please contact us.` | 400 | `type: unified` without the ENABLE_UNIFIED flag. |\n| `Your account is currently on hold. Please contact us to purchase a virtual number.` | 400 | Account is on hold. |\n| `Maximum virtual numbers reached. Contact us to request increaed quota.` | 400 | Number limit reached (default 2). |\n| `No payment method on file. Please add a payment method in the dashboard.` | 400 | No card on file. |\n| `Invalid subscription plan` | 400 | Subscription plan could not be resolved. |\n| `Your payment was declined by your bank.` | 400 | Card charge declined. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"country\": \"AU\",\n  \"type\": \"standard\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": \"Virtual number purchased\",\n  \"virtualnumber\": {\n    \"id\": \"683554c596937cc4b90f5cf7\",\n    \"number\": \"61400000000\",\n    \"number_formatted\": \"0400 000 000\",\n    \"country\": \"AU\"\n  }\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Invalid country. Currently only AU is supported for API requests.\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Delete Virtual Number",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/virtualnumber/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "virtualnumber",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Virtual number record id."
                }
              ]
            },
            "description": "Release a virtual number and cancel its subscription.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid virtual number ID format` | 400 | `id` is not a valid id. |\n| `Virtual number not found` | 400 | No such virtual number. |\n| `Unauthorized - you do not own this virtual number` | 400 | Number not owned by this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": \"Virtual number released\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Email Domains",
      "item": [
        {
          "name": "List Domains",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_dingo}}/domain",
              "host": [
                "{{base_url_dingo}}"
              ],
              "path": [
                "domain"
              ]
            },
            "description": "List the email domains registered on your account.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `domains` | array | Registered domains. |\n| `domains[].id` | string | Domain record id. |\n| `domains[].domain` | string | Domain name. |\n| `domains[].subdomain` | string | Sending subdomain. |\n| `domains[].status` | string | Verification status. |\n| `domains[].dkim_names` | array | DKIM record names. |\n| `domains[].verification` | string | SES verification state. |\n| `count` | integer | Number of domains. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"domains\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf7\",\n      \"domain\": \"example.com\",\n      \"subdomain\": \"email\",\n      \"status\": \"verified\",\n      \"dkim_names\": [\n        \"k1._domainkey\"\n      ],\n      \"verification\": \"verified\"\n    }\n  ],\n  \"count\": 1\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Get Domain",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_dingo}}/domain/:id",
              "host": [
                "{{base_url_dingo}}"
              ],
              "path": [
                "domain",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Domain record id."
                }
              ]
            },
            "description": "Fetch a single email domain, including its DKIM and verification details.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `domain` | object | id, domain, subdomain, status, dkim_names, verification. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Failed (Invalid Domain ID format)` | 400 | The `id` path segment is not a 24-character hexadecimal id. |\n| `Failed (Domain not found or unauthorized)` | 400 | No such domain, or not owned by this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"domain\": {\n    \"id\": \"683554c596937cc4b90f5cf7\",\n    \"domain\": \"example.com\",\n    \"subdomain\": \"email\",\n    \"status\": \"verified\",\n    \"dkim_names\": [\n      \"k1._domainkey\"\n    ],\n    \"verification\": \"verified\"\n  }\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Create Domain",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_dingo}}/domain",
              "host": [
                "{{base_url_dingo}}"
              ],
              "path": [
                "domain"
              ]
            },
            "description": "Register a new email domain. Email must be enabled and your plan's domain limit must not be exceeded.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `domain` | string | Yes | Domain name (valid domain format). |\n| `subdomain` | string | No | Sending subdomain; letters, numbers, hyphens. Default `email`. If a full host is supplied it is auto-corrected and a `warning` is returned. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `message` | object | `{id, domain}`. |\n| `warning` | string | Present only when the subdomain was auto-corrected. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Email not enabled. Please contact us.` | 400 | Email is not enabled on this account. |\n| `Domain limit reached. Your plan allows {n} domain{s}. Please upgrade your plan or contact us.` | 400 | Plan domain limit reached. |\n| `Missing Domain` | 400 | `domain` not supplied. |\n| `Invalid Domain` | 400 | `domain` is not a valid domain. |\n| `Domain already registered. Please contact us.` | 400 | Domain already exists. |\n| `Invalid subdomain. Use only letters, numbers, and hyphens.` | 400 | `subdomain` contains invalid characters. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"domain\": \"example.com\",\n  \"subdomain\": \"email\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": {\n    \"id\": \"683554c596937cc4b90f5cf7\",\n    \"domain\": \"example.com\"\n  }\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Invalid Domain\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Mailboxes",
      "description": "Hosted mailboxes on the shared mailbox domain — send-and-receive addresses that need no domain of your own. Your plan's mailbox quota caps how many active mailboxes the account may have. Mailboxes are never deleted via the API: `DELETE` returns `Unsupported method. Please see our API Docs` (HTTP 400). Disable a mailbox instead; mailboxes are removed only when the account closes.",
      "item": [
        {
          "name": "List Mailboxes",
          "request": {
            "method": "GET",
            "header": [ { "key": "Content-Type", "value": "application/json" } ],
            "url": {
              "raw": "{{base_url_dingo}}/mailboxes",
              "host": [ "{{base_url_dingo}}" ],
              "path": [ "mailboxes" ]
            },
            "description": "List the hosted mailboxes on your account, active and inactive.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `mailboxes` | array | One object per mailbox. |\n| `mailboxes[].id` | string | Mailbox id (use with Enable / Disable Mailbox). |\n| `mailboxes[].address` | string | Full mailbox address. |\n| `mailboxes[].active` | boolean | `false` when the mailbox is disabled — inbound to it is dropped and it cannot send. |\n| `mailboxes[].created` | integer | Unix timestamp the mailbox was created, or `null`. |\n| `count` | integer | Number of mailboxes returned. |\n| `error` | string | Empty on success. |\n\n#### Errors\nNo endpoint-specific errors — authentication errors per the intro's 401 table.",
            "body": {
              "mode": "raw",
              "options": { "raw": { "language": "json" } },
              "raw": "{}"
            }
          },
          "response": [
            { "name": "200 OK", "code": 200, "status": "OK", "body": "{\n  \"error\": \"\",\n  \"count\": 2,\n  \"mailboxes\": [\n    {\n      \"id\": \"66a1f2c3d4e5f6a7b8c9d0e1\",\n      \"address\": \"newsletter@mydingo.au\",\n      \"active\": true,\n      \"created\": 1753142400\n    },\n    {\n      \"id\": \"66a1f2c3d4e5f6a7b8c9d0e2\",\n      \"address\": \"old@mydingo.au\",\n      \"active\": false,\n      \"created\": 1750464000\n    }\n  ]\n}" },
            { "name": "401 Unauthorized", "code": 401, "status": "Unauthorized", "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}" }
          ]
        },
        {
          "name": "Create Mailbox",
          "request": {
            "method": "POST",
            "header": [ { "key": "Content-Type", "value": "application/json" } ],
            "url": {
              "raw": "{{base_url_dingo}}/mailboxes",
              "host": [ "{{base_url_dingo}}" ],
              "path": [ "mailboxes" ]
            },
            "description": "Create a hosted mailbox. The new mailbox is active immediately and can send and receive at `<name>@mydingo.au`. Creation counts against your plan's mailbox quota (active mailboxes only).\n\n#### Request body\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `name` | string | yes | The local part (the text before the `@`). Lowercased. Letters, numbers and `. _ -` only, must start and end with a letter or number, maximum 64 characters. `+` is not allowed. Reserved names (role addresses such as `postmaster`, `abuse`, `admin`, and brand terms) are rejected. Must be globally unique across all accounts. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `mailbox.id` | string | The new mailbox id. |\n| `mailbox.address` | string | The full mailbox address. |\n| `mailbox.active` | boolean | Always `true` on create. |\n| `mailbox.created` | integer | Unix timestamp. |\n| `message` | string | `Mailbox created`. |\n| `error` | string | Empty on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Mailboxes are not included in your plan` | 400 | The account's mailbox quota is 0. |\n| `Unable to create mailboxes at this time. Please contact us.` | 400 | Please contact us |\n| `Mailbox limit reached` | 400 | The account already has as many active mailboxes as its quota allows. |\n| `Name Required. Please see our API Docs` | 400 | `name` missing or empty. |\n| `Mailbox name may only contain letters, numbers, and . _ - characters.` | 400 | `name` fails the format rule. |\n| `Mailbox name must be 64 characters or fewer.` | 400 | `name` longer than 64 characters. |\n| `That mailbox name is reserved. Please choose another name.` | 400 | `name` is on the reserved list. |\n| `That mailbox is already taken. Please choose another name.` | 400 | The address exists on any account (including disabled mailboxes). |",
            "body": {
              "mode": "raw",
              "options": { "raw": { "language": "json" } },
              "raw": "{\n  \"name\": \"newsletter\"\n}"
            }
          },
          "response": [
            { "name": "200 OK", "code": 200, "status": "OK", "body": "{\n  \"error\": \"\",\n  \"message\": \"Mailbox created\",\n  \"mailbox\": {\n    \"id\": \"66a1f2c3d4e5f6a7b8c9d0e1\",\n    \"address\": \"newsletter@mydingo.au\",\n    \"active\": true,\n    \"created\": 1753142400\n  }\n}" },
            { "name": "400 Bad Request", "code": 400, "status": "Bad Request", "body": "{\n  \"error\": \"That mailbox is already taken. Please choose another name.\"\n}" },
            { "name": "401 Unauthorized", "code": 401, "status": "Unauthorized", "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}" }
          ]
        },
        {
          "name": "Enable / Disable Mailbox",
          "request": {
            "method": "POST",
            "header": [ { "key": "Content-Type", "value": "application/json" } ],
            "url": {
              "raw": "{{base_url_dingo}}/mailboxes/:id",
              "host": [ "{{base_url_dingo}}" ],
              "path": [ "mailboxes", ":id" ],
              "variable": [ { "key": "id", "value": "", "description": "Mailbox id (from List Mailboxes)." } ]
            },
            "description": "Enable or disable a hosted mailbox. A disabled mailbox drops inbound email, cannot be used as a sender address, and does not count toward the mailbox quota — but keeps its address reserved to your account. Re-enabling is blocked while the account is already at its active-mailbox quota. There is no delete: mailboxes are removed only when the account closes.\n\n#### Request body\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `active` | boolean | yes | `true` to enable, `false` to disable. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `mailbox` | object | The updated mailbox (`id`, `address`, `active`, `created`). |\n| `message` | string | `Mailbox updated`. |\n| `error` | string | Empty on success. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid Mailbox ID` | 400 | The path id is not a 24-character hex id. |\n| `Mailbox Not Found` | 400 | No mailbox with that id on this account. |\n| `active Required. Please see our API Docs` | 400 | `active` missing or not a boolean. |\n| `Mailbox limit reached` | 400 | Enabling would exceed the account's active-mailbox quota. |",
            "body": {
              "mode": "raw",
              "options": { "raw": { "language": "json" } },
              "raw": "{\n  \"active\": false\n}"
            }
          },
          "response": [
            { "name": "200 OK", "code": 200, "status": "OK", "body": "{\n  \"error\": \"\",\n  \"message\": \"Mailbox updated\",\n  \"mailbox\": {\n    \"id\": \"66a1f2c3d4e5f6a7b8c9d0e1\",\n    \"address\": \"newsletter@mydingo.au\",\n    \"active\": false,\n    \"created\": 1753142400\n  }\n}" },
            { "name": "401 Unauthorized", "code": 401, "status": "Unauthorized", "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}" }
          ]
        }
      ]
    },
    {
      "name": "Contacts",
      "item": [
        {
          "name": "Create Contact List",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/lists",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "lists"
              ]
            },
            "description": "Create a contact list.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `name` | string | Yes | List name. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `list.id` | string | New list id. |\n| `list.name` | string | List name. |\n| `list.status` | string | `active` on creation. |\n| `list.count` | integer | Contact count (0 for a new list). |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Missing required field: name` | 400 | `name` not supplied or empty. |\n| `List update is not supported` | 400 | POSTing to a list id (`/lists/{id}`) — renaming or changing a list's status is not available via the API; manage it in the dashboard. |\n\n#### Notes\n- Lists are create-and-delete only through the API. There is no rename or archive endpoint.\n- Deleting a list also deletes every contact in it (see Delete List).",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"name\": \"VIP Customers\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"list\": {\n    \"id\": \"683554c596937cc4b90f5cf7\",\n    \"name\": \"VIP Customers\",\n    \"status\": \"active\",\n    \"count\": 0\n  },\n  \"message\": \"List created\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Missing required field: name\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "List Contact Lists",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/lists",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "lists"
              ]
            },
            "description": "List all contact lists on your account, each with its current contact count.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `lists` | array | Contact lists. |\n| `lists[].id` | string | List id. |\n| `lists[].name` | string | List name. |\n| `lists[].status` | string | `active` or `archived`. |\n| `lists[].count` | integer | Number of contacts in the list. |\n| `count` | integer | Number of lists. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"lists\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf7\",\n      \"name\": \"VIP Customers\",\n      \"status\": \"active\",\n      \"count\": 128\n    }\n  ],\n  \"count\": 1\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Get Contact List",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/lists/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "lists",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Contact list id (24-hex)."
                }
              ]
            },
            "description": "Get a single contact list by id.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `list.id` | string | List id. |\n| `list.name` | string | List name. |\n| `list.status` | string | `active` or `archived`. |\n| `list.count` | integer | Number of contacts in the list. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid list ID format` | 400 | The id is not a 24-hex value. |\n| `List not found` | 400 | No such list on this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"list\": {\n    \"id\": \"683554c596937cc4b90f5cf7\",\n    \"name\": \"VIP Customers\",\n    \"status\": \"active\",\n    \"count\": 128\n  }\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"List not found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Delete Contact List",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/lists/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "lists",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Contact list id (24-hex)."
                }
              ]
            },
            "description": "Delete a contact list **and every contact in it**. This cannot be undone.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid list ID format` | 400 | The id is not a 24-hex value. |\n| `List not found` | 400 | No such list on this account. |\n\n#### Notes\n- The delete cascades: every contact whose `list_id` is this list is removed along with the list.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": \"List and its contacts deleted\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"List not found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Create Contact",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/contacts",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "contacts"
              ]
            },
            "description": "Create one contact, or many in a single call.\n\n- **Single:** send the contact fields at the top level.\n- **Batch:** send a `contacts` array. Every row is inserted into the one `list_id`, and the response reports per-row results. Batch is best-effort — a row that isn't a JSON object is skipped and reported in `results[]`; the rest still insert. Max 1000 rows per call.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `list_id` | string | Yes | Target list id; must belong to your account. |\n| `number` | string | No | Phone number; normalised to 614… format on save. |\n| `first_name` | string | No | Contact's first name. |\n| `last_name` | string | No | Contact's last name. |\n| `email` | string | No | Contact's email address. |\n| `meta` | object | No | Custom fields — arbitrary key/value pairs. Scalar values only (array/object values are dropped); `.` and `$` in keys are replaced with `_`. |\n| `contacts` | array | No | Batch mode: array of contact objects, each carrying the fields above except `list_id`. Presence of this field switches to batch mode. |\n\n#### Response fields (single)\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `contact` | object | The created contact: `id`, `list_id`, `number`, `first_name`, `last_name`, `email`, `meta`. |\n| `message` | string | Confirmation message. |\n\n#### Response fields (batch)\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty — a batch always returns HTTP 200; per-row failures are in `results`. |\n| `created` | integer | Rows inserted. |\n| `failed` | integer | Rows skipped. |\n| `results` | array | Per row: `{index, id}` on success or `{index, error}` on failure. |\n| `message` | string | `Batch processed`. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Missing required field: list_id` | 400 | `list_id` not supplied. |\n| `Invalid list ID format` | 400 | `list_id` not a 24-hex value. |\n| `List not found` | 400 | The list doesn't exist or isn't yours. |\n| `contacts must be a non-empty array` | 400 | Batch mode with an empty or non-array `contacts`. |\n| `Batch too large. Maximum 1000 contacts per request` | 400 | Batch `contacts` has more than 1000 rows. |\n| `Invalid contact object` | (per-row) | A `contacts[]` row is not an object — reported in `results[].error`, not at the top level (HTTP stays 200). |\n\n#### Notes\n- Custom fields have no per-account schema; any key you send under `meta` is stored as-is.\n- Create is not deduplicated — re-sending the same contact creates a duplicate.\n- A batch targets a single list; mixing lists in one call is not supported.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"list_id\": \"683554c596937cc4b90f5cf7\",\n  \"number\": \"0412333555\",\n  \"first_name\": \"Jane\",\n  \"last_name\": \"Smith\",\n  \"email\": \"jane@example.com\",\n  \"meta\": {\n    \"company\": \"Acme\",\n    \"plan\": \"gold\"\n  }\n}"
            }
          },
          "response": [
            {
              "name": "200 OK (single)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"contact\": {\n    \"id\": \"683554c596937cc4b90f5cf8\",\n    \"list_id\": \"683554c596937cc4b90f5cf7\",\n    \"number\": \"61412333555\",\n    \"first_name\": \"Jane\",\n    \"last_name\": \"Smith\",\n    \"email\": \"jane@example.com\",\n    \"meta\": {\n      \"company\": \"Acme\",\n      \"plan\": \"gold\"\n    }\n  },\n  \"message\": \"Contact created\"\n}"
            },
            {
              "name": "200 OK (batch)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"created\": 2,\n  \"failed\": 1,\n  \"results\": [\n    { \"index\": 0, \"id\": \"683554c596937cc4b90f5cf8\" },\n    { \"index\": 1, \"id\": \"683554c596937cc4b90f5cf9\" },\n    { \"index\": 2, \"error\": \"Invalid contact object\" }\n  ],\n  \"message\": \"Batch processed\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Missing required field: list_id\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "List Contacts",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/contacts",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "contacts"
              ],
              "query": [
                {
                  "key": "list_id",
                  "value": "",
                  "description": "Optional. Restrict to one list (24-hex).",
                  "disabled": true
                },
                {
                  "key": "search",
                  "value": "",
                  "description": "Optional. Case-insensitive substring match on first name, last name, number, or email.",
                  "disabled": true
                },
                {
                  "key": "after",
                  "value": "",
                  "description": "24-hex cursor — the `id` of the last contact on the previous page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "description": "Page size. Plain list: 1–1000 (default 100). Search: 1–500 (default 500).",
                  "disabled": true
                }
              ]
            },
            "description": "List contacts, newest first, or search them.\n\n- **Without `search`:** a keyset-paginated list, optionally filtered by `list_id`.\n- **With `search`:** a substring match over name / number / email, also keyset-paginated.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `contacts` | array | Contacts on this page. |\n| `contacts[].id` | string | Contact id. |\n| `contacts[].list_id` | string | Owning list id. |\n| `contacts[].number` | string | Phone number (614… format). |\n| `contacts[].first_name` | string | First name. |\n| `contacts[].last_name` | string | Last name. |\n| `contacts[].email` | string | Email address. |\n| `contacts[].meta` | object | Custom fields (empty object when none). |\n| `count` | integer | Contacts on this page. |\n| `next_page` | string | Relative path for the next page; empty when there are no further results. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid list ID format` | 400 | `list_id` is not a 24-hex value. |\n| `Failed (Invalid Page)` | 400 | `after` is not a valid 24-hex cursor. |\n\n#### Notes\n- Plain-list page size defaults to 100 (`limit` 1–1000). Search page size defaults to 500 (`limit` 1–500).\n- `next_page` is present only when a full page was returned; follow it until it is empty. The paging examples below use `limit=2` to stay short.\n- Search is inherently a substring scan — for a rare term over a very large contact book a page may take longer to fill.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"contacts\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf8\",\n      \"list_id\": \"683554c596937cc4b90f5cf7\",\n      \"number\": \"61412333555\",\n      \"first_name\": \"Jane\",\n      \"last_name\": \"Smith\",\n      \"email\": \"jane@example.com\",\n      \"meta\": {\n        \"company\": \"Acme\"\n      }\n    },\n    {\n      \"id\": \"683554c596937cc4b90f5cf6\",\n      \"list_id\": \"683554c596937cc4b90f5cf7\",\n      \"number\": \"61412333777\",\n      \"first_name\": \"Tom\",\n      \"last_name\": \"Jones\",\n      \"email\": \"tom@example.com\",\n      \"meta\": {}\n    }\n  ],\n  \"count\": 2,\n  \"next_page\": \"/api/v5/contacts?after=683554c596937cc4b90f5cf6\"\n}"
            },
            {
              "name": "200 OK (Last Page)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"contacts\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf4\",\n      \"list_id\": \"683554c596937cc4b90f5cf7\",\n      \"number\": \"61412333999\",\n      \"first_name\": \"Amy\",\n      \"last_name\": \"Lee\",\n      \"email\": \"amy@example.com\",\n      \"meta\": {}\n    }\n  ],\n  \"count\": 1,\n  \"next_page\": \"\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Get Contact",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/contacts/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "contacts",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Contact id (24-hex)."
                }
              ]
            },
            "description": "Get a single contact by id.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `contact` | object | The contact: `id`, `list_id`, `number`, `first_name`, `last_name`, `email`, `meta`. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid contact ID format` | 400 | The id is not a 24-hex value. |\n| `Contact not found` | 400 | No such contact on this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"contact\": {\n    \"id\": \"683554c596937cc4b90f5cf8\",\n    \"list_id\": \"683554c596937cc4b90f5cf7\",\n    \"number\": \"61412333555\",\n    \"first_name\": \"Jane\",\n    \"last_name\": \"Smith\",\n    \"email\": \"jane@example.com\",\n    \"meta\": {\n      \"company\": \"Acme\"\n    }\n  }\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Contact not found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Update Contact",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/contacts/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "contacts",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Contact id (24-hex)."
                }
              ]
            },
            "description": "Update fields on a contact. Only the fields you send change.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `number` | string | No | Phone number; normalised to 614… on save. |\n| `first_name` | string | No | First name. |\n| `last_name` | string | No | Last name. |\n| `email` | string | No | Email address. |\n| `meta` | object | No | Custom fields. **Replaces** the entire meta object — send the full desired set, not a partial patch. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `contact` | object | The updated contact. |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid contact ID format` | 400 | The id is not a 24-hex value. |\n| `Contact not found` | 400 | No such contact on this account. |\n| `Nothing to update` | 400 | No recognised field supplied. |\n\n#### Notes\n- `meta` is replaced wholesale, not merged; omit it to leave custom fields untouched.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"email\": \"jane.smith@example.com\"\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"contact\": {\n    \"id\": \"683554c596937cc4b90f5cf8\",\n    \"list_id\": \"683554c596937cc4b90f5cf7\",\n    \"number\": \"61412333555\",\n    \"first_name\": \"Jane\",\n    \"last_name\": \"Smith\",\n    \"email\": \"jane.smith@example.com\",\n    \"meta\": {\n      \"company\": \"Acme\"\n    }\n  },\n  \"message\": \"Contact updated\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Nothing to update\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Delete Contact",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/contacts/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "contacts",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Contact id (24-hex)."
                }
              ]
            },
            "description": "Delete a contact.\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid contact ID format` | 400 | The id is not a 24-hex value. |\n| `Contact not found` | 400 | No such contact on this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": \"Contact deleted\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Contact not found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Webhooks",
      "description": "Create and manage outbound webhook configurations. A webhook POSTs to your URL when a platform event fires.\n\nTwo formats exist, selected by `api_version` on create. **Legacy** (`api_version: 1`, the default) subscribes by `type` and posts a URL-encoded body. **Current** (`api_version: 2`) subscribes to individual events by name and posts a signed JSON body. Use the current format for new integrations.\n\nThe payload your endpoint receives, the signing scheme and the retry behaviour are documented on the Webhooks page in your dashboard, not here. These four endpoints are the management API only.\n\nAll endpoints use `{{base_url_sms}}`.",
      "item": [
        {
          "name": "List Webhooks",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/webhooks",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "webhooks"
              ],
              "query": [
                {
                  "key": "type",
                  "value": "",
                  "description": "Restrict to one type. One of `sms_events`, `sms_inbound`, `sms_optout`, `email_inbound`, `email_events`, `email_alerts`, `account_balance`. Matches version 1 (legacy) webhooks only; a version 2 webhook stores no public `type` and is never matched by this filter.",
                  "disabled": true
                }
              ]
            },
            "description": "List every outbound webhook configuration on your account, including disabled and temporarily-suppressed ones (this is a management view). Optionally filter by `type`.\n\n#### Query parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `type` | string | No | Restrict to one type. One of `sms_events`, `sms_inbound`, `sms_optout`, `email_inbound`, `email_events`, `email_alerts`, `account_balance`. Matches version 1 (legacy) webhooks only; a version 2 webhook stores no public `type` and is never matched by this filter. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `webhooks` | array | Webhook configurations on the account. |\n| `webhooks[].id` | string | Webhook id (24-hex). |\n| `webhooks[].api_version` | integer | `1` (legacy) or `2` (current). |\n| `webhooks[].type` | string\\|null | One of `sms_events`, `sms_inbound`, `sms_optout`, `email_inbound`, `email_events`, `email_alerts`, `account_balance` for a version 1 webhook; `null` for a version 2 webhook, since it subscribes by individual event name rather than by type. |\n| `webhooks[].url` | string | Destination URL the payload is POSTed to. |\n| `webhooks[].version` | integer | Payload format version. Kept for backwards compatibility; always equal to `api_version`. |\n| `webhooks[].threshold` | integer\\|null | Balance threshold; set only when the webhook fires on a low-balance condition, otherwise `null`. |\n| `webhooks[].events` | array | Subscribed events; populated for `email_events` webhooks (subset of `delivery`, `engagement`, `unsubscribe`), for `email_alerts` webhooks (subset of `bounce_rate`, `complaint_rate`), and for every version 2 webhook (its full subscribed event list), otherwise `[]`. |\n| `webhooks[].https_only` | boolean | Version 2 only. Whether HTTPS and certificate verification are required. Always `false` for a version 1 webhook. |\n| `webhooks[].header_names` | array | Version 2 only. Names of the custom headers sent with each delivery. Values are never returned. `[]` for a version 1 webhook, and for a version 2 webhook with no custom headers. |\n| `webhooks[].enabled` | boolean | Whether the webhook is currently enabled. |\n| `webhooks[].auto_disabled` | boolean | `true` when the webhook was disabled by repeated delivery failures. Only applies to version 2 webhooks. |\n| `count` | integer | Number of webhooks returned. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid type. Must be one of: sms_events, sms_inbound, sms_optout, email_inbound, email_events, email_alerts, account_balance` | 400 | `type` filter is not an allowed value. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"webhooks\": [\n    {\n      \"id\": \"683554c596937cc4b90f5cf7\",\n      \"api_version\": 1,\n      \"type\": \"sms_events\",\n      \"url\": \"https://example.com/dlr\",\n      \"version\": 1,\n      \"threshold\": null,\n      \"events\": [],\n      \"https_only\": false,\n      \"header_names\": [],\n      \"enabled\": true,\n      \"auto_disabled\": false\n    },\n    {\n      \"id\": \"7a1b2c3d4e5f60718293a4b5\",\n      \"api_version\": 1,\n      \"type\": \"email_events\",\n      \"url\": \"https://example.com/email-events\",\n      \"version\": 1,\n      \"threshold\": null,\n      \"events\": [\n        \"delivery\",\n        \"engagement\"\n      ],\n      \"https_only\": false,\n      \"header_names\": [],\n      \"enabled\": true,\n      \"auto_disabled\": false\n    },\n    {\n      \"id\": \"9f8e7d6c5b4a39281706f5e4\",\n      \"api_version\": 1,\n      \"type\": \"email_alerts\",\n      \"url\": \"https://example.com/deliverability-alerts\",\n      \"version\": 1,\n      \"threshold\": null,\n      \"events\": [\n        \"bounce_rate\",\n        \"complaint_rate\"\n      ],\n      \"https_only\": false,\n      \"header_names\": [],\n      \"enabled\": true,\n      \"auto_disabled\": false\n    },\n    {\n      \"id\": \"68be1f0c9a3b4c0012ab34cd\",\n      \"api_version\": 2,\n      \"type\": null,\n      \"url\": \"https://example.com/hooks/sms\",\n      \"version\": 2,\n      \"threshold\": null,\n      \"events\": [\n        \"sms.delivered\",\n        \"sms.failed\"\n      ],\n      \"https_only\": true,\n      \"header_names\": [\"Authorization\"],\n      \"enabled\": true,\n      \"auto_disabled\": false\n    }\n  ],\n  \"count\": 4\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Create Webhook",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/webhooks",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "webhooks"
              ]
            },
            "description": "Create an outbound webhook configuration. Two contracts, selected by `api_version`. `api_version: 1` (the default, \"legacy\") requires `type` and posts a URL-encoded body. `api_version: 2` (the current contract) requires `events` and posts a signed JSON body; see the Webhooks page in your dashboard for the full payload contract - this is the management endpoint only.\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `api_version` | integer | No | `1` (default) or `2`. Selects the webhook contract. |\n| `type` | string | Yes for `api_version` 1 | One of `sms_events`, `sms_inbound`, `sms_optout`, `email_inbound`, `email_events`, `email_alerts`, `account_balance`. Not accepted for `api_version` 2; returned as `null`. |\n| `url` | string | Yes | Destination URL. `http://` (or `https://` when `https_only` is true) is prepended if no scheme is given. The host is resolved and rejected if it does not resolve, or if any resolved address is private, loopback, link-local, carrier-grade NAT, multicast or reserved. Applies to both contracts. |\n| `threshold` | integer | Conditional | Required for `type=account_balance` (version 1), or when `events` contains `account.balance_low` (version 2): fires when the account balance is at or below this value (must be > 0). Ignored otherwise. |\n| `events` | array | Conditional | Version 1: required for `type=email_events` (subset of `delivery`, `engagement`, `unsubscribe`) or `type=email_alerts` (subset of `bounce_rate`, `complaint_rate`). Version 2: required, a non-empty subset of `sms.sent`, `sms.delivered`, `sms.read`, `sms.failed`, `sms.cancelled`, `sms.inbound`, `sms.optout`, `email.delivered`, `email.bounced`, `email.complained`, `email.blocked`, `email.invalid`, `email.attachment_failed`, `email.too_many_recipients`, `email.blocked_dangerous_link`, `email.enquiry`, `email.opened`, `email.clicked`, `email.unsubscribed`, `email.inbound`, `email.alert.bounce_rate_warning`, `email.alert.bounce_rate_critical`, `email.alert.bounce_rate_cleared`, `email.alert.complaint_rate_warning`, `email.alert.complaint_rate_critical`, `email.alert.complaint_rate_cleared`, `account.balance_low`. `account.balance_low` requires `threshold`. What each event delivers is documented on the Webhooks page in your dashboard. Duplicates are collapsed. |\n| `https_only` | boolean | No | Version 2 only. Defaults to `true`. Requires an `https://` URL and verifies the certificate at delivery time. |\n| `headers` | object | No | Version 2 only. Up to 3 custom request headers, as an object of header name to value, sent with every delivery and every retry of this webhook - for example `{\"Authorization\": \"Bearer abc123\"}`. Names use the HTTP token character set (letters, digits and ``! # % & ' * + . ^ _ ~ -``, plus backtick and \\|), max 64 characters; values are printable ASCII, max 1024 characters. Headers the platform sets (`Host`, `Content-Type`, `Content-Length`, `Transfer-Encoding`, `Connection`, `Expect`, `Accept-Encoding`, `User-Agent`, `X-Signature`) and any name beginning `X-Webhook-` are rejected. Values are write-only: they are never returned by any endpoint and are redacted from your delivery logs. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `webhook` | object | The created webhook (`id`, `api_version`, `type`, `url`, `version`, `threshold`, `events`, `https_only`, `header_names`, `enabled`, `auto_disabled`). |\n| `webhook.header_names` | array | Version 2 only. The names of the custom headers stored for this webhook. Values are never returned. |\n| `secret` | string | Version 2 only. The signing secret, returned once and never again. |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Missing required field: type` | 400 | `api_version` 1 (or omitted) with `type` not supplied. |\n| `Invalid type. Must be one of: sms_events, sms_inbound, sms_optout, email_inbound, email_events, email_alerts, account_balance` | 400 | `type` is not an allowed value. |\n| `Missing required field: url` | 400 | `url` not supplied. |\n| `Balance webhooks require a numeric threshold greater than 0` | 400 | `type=account_balance` with a missing or non-positive `threshold`. |\n| `Email webhooks require a non-empty events array (subset of: delivery, engagement, unsubscribe)` | 400 | `type=email_events` with `events` absent, not an array, or empty. |\n| `Invalid event. Must be one of: delivery, engagement, unsubscribe` | 400 | `type=email_events` with an `events` value outside the allowlist. |\n| `Email alert webhooks require a non-empty events array (subset of: bounce_rate, complaint_rate)` | 400 | `type=email_alerts` with `events` absent, not an array, or empty. |\n| `Invalid event. Must be one of: bounce_rate, complaint_rate` | 400 | `type=email_alerts` with an `events` value outside the alert allowlist. |\n| `Invalid URL format` | 400 | `url` fails URL validation. |\n| `URL must use http or https protocol` | 400 | `url` scheme is not http/https. |\n| `URL must have a valid domain` | 400 | `url` has no host. |\n| `Invalid URL protocol` | 400 | `url` contains a `file://` protocol. |\n| `Cannot use localhost URLs` | 400 | `url` host is localhost/127.0.0.1/::1. |\n| `Invalid URL` | 400 | `url` targets the cloud metadata address (169.254.169.254). |\n| `Invalid api_version. Must be 1 or 2` | 400 | `api_version` present and neither 1 nor 2. |\n| `Version 2 webhooks require a non-empty events array` | 400 | `api_version` 2 with `events` missing, not an array, or empty. |\n| `Invalid event. See the Webhooks documentation for the full list of event names.` | 400 | An entry in `events` is not a recognised event name (version 2). |\n| `account.balance_low requires a numeric threshold greater than 0` | 400 | `account.balance_low` subscribed (version 2) without a positive numeric `threshold`. |\n| `Webhooks require an https:// URL when HTTPS is required.` | 400 | `https_only` is true and the URL is not `https://`. |\n| `Endpoint URI host could not be resolved.` | 400 | The URL's host has no A or AAAA record. Applies to both contracts. |\n| `Endpoint URI resolves to an address we cannot send to. Use a publicly reachable host.` | 400 | Any address the host resolves to is private, loopback, link-local, carrier-NAT, multicast or reserved. Applies to both contracts. |\n| `headers must be an object of header name to value` | 400 | `headers` is present but is not an object. |\n| `Each custom header needs both a name and a value.` | 400 | A header entry has an empty name or an empty value. |\n| `Custom header names are limited to 64 characters.` | 400 | A header name is too long. |\n| `Custom header values are limited to 1024 characters.` | 400 | A header value is too long. |\n| Custom header names may contain letters, digits and ! # % & ' * + . ^ _ ` \\| ~ - only. | 400 | A header name is outside the HTTP token character set. The message carries a literal backtick and vertical bar, so this row is not code-formatted: a code span cannot render either character. |\n| `Custom header values may contain printable ASCII characters only.` | 400 | A header value carries a control character, including CR or LF. |\n| `The header <name> is set by the platform and cannot be overridden.` | 400 | A header name is one the platform sets, or begins `X-Webhook-`. |\n| `Custom header <name> is listed more than once.` | 400 | Two header entries share a name, compared case-insensitively. |\n| `A webhook may carry at most 3 custom headers.` | 400 | More than three header entries were supplied. |\n| `Invalid request parameters` | 400 | A header name beginning `$` - rejected by the API's request guard before this endpoint runs. |\n\n#### Example request body (version 2)\n```json\n{\n  \"api_version\": 2,\n  \"url\": \"https://example.com/hooks/sms\",\n  \"events\": [\"sms.delivered\", \"sms.failed\"],\n  \"https_only\": true,\n  \"headers\": {\n    \"Authorization\": \"Bearer abc123\"\n  }\n}\n```\n\n#### Notes\n- Use `api_version: 2` for new integrations. Version 1 is unchanged and stays available, so existing callers need no edit.\n- A webhook is created enabled and starts receiving events immediately.\n- `url`, `type`, `events` and `headers` cannot be changed after creation. Enabling and disabling is the only mutation; to change anything else, delete the webhook and create a replacement.\n- Duplicates are allowed. Several webhooks may point at the same URL, and several may subscribe to the same event.\n- Custom headers are sent as supplied, on every attempt. If your endpoint rejects a stale credential with a non-2xx status, that counts as a failed delivery and the webhook is auto-disabled after 5 consecutive failed deliveries like any other persistent failure.\n- Version 2 only: `secret` appears in this response and in no other response, ever. Store it before you discard the response body. A webhook whose secret is lost has to be deleted and recreated.\n- No event requires an account flag, and no webhook is billable. The `email.alert.*` events cover account-level bounce and complaint rates crossing a band; per-message email outcomes are the `email.*` events instead.\n- What your endpoint receives, how to verify `X-Signature`, and the retry and auto-disable schedule are on the Webhooks page in your dashboard. This endpoint reference deliberately does not repeat them.",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"type\": \"email_events\",\n  \"url\": \"https://example.com/email-events\",\n  \"events\": [\n    \"delivery\",\n    \"engagement\"\n  ]\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"webhook\": {\n    \"id\": \"7a1b2c3d4e5f60718293a4b5\",\n    \"type\": \"email_events\",\n    \"url\": \"https://example.com/email-events\",\n    \"version\": 1,\n    \"threshold\": null,\n    \"events\": [\n      \"delivery\",\n      \"engagement\"\n    ],\n    \"enabled\": true\n  },\n  \"message\": \"Webhook created\"\n}"
            },
            {
              "name": "200 OK (email alerts)",
              "code": 200,
              "status": "OK",
              "originalRequest": {
                "method": "POST",
                "body": {
                  "mode": "raw",
                  "options": {
                    "raw": {
                      "language": "json"
                    }
                  },
                  "raw": "{\n  \"type\": \"email_alerts\",\n  \"url\": \"https://example.com/deliverability-alerts\",\n  \"events\": [\n    \"bounce_rate\",\n    \"complaint_rate\"\n  ]\n}"
                }
              },
              "body": "{\n  \"error\": \"\",\n  \"webhook\": {\n    \"id\": \"9f8e7d6c5b4a39281706f5e4\",\n    \"type\": \"email_alerts\",\n    \"url\": \"https://example.com/deliverability-alerts\",\n    \"version\": 1,\n    \"threshold\": null,\n    \"events\": [\n      \"bounce_rate\",\n      \"complaint_rate\"\n    ],\n    \"enabled\": true\n  },\n  \"message\": \"Webhook created\"\n}"
            },
            {
              "name": "200 OK (version 2)",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"webhook\": {\n    \"id\": \"68be1f0c9a3b4c0012ab34cd\",\n    \"api_version\": 2,\n    \"type\": null,\n    \"url\": \"https://example.com/hooks/sms\",\n    \"version\": 2,\n    \"threshold\": null,\n    \"events\": [\n      \"sms.delivered\",\n      \"sms.failed\"\n    ],\n    \"https_only\": true,\n    \"header_names\": [\"Authorization\"],\n    \"enabled\": true,\n    \"auto_disabled\": false\n  },\n  \"secret\": \"Zx7qWm2PdKfR9tHyBnLcVaJsEuQoXiMr4T3gY6wZ\",\n  \"message\": \"Webhook created. Store the secret now - it is not returned again.\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Email webhooks require a non-empty events array (subset of: delivery, engagement, unsubscribe)\"\n}"
            },
            {
              "name": "400 Bad Request (invalid event)",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Invalid event. See the Webhooks documentation for the full list of event names.\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Enable / Disable Webhook",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/webhooks/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "webhooks",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Webhook id (24-hex)."
                }
              ]
            },
            "description": "Enable or disable an existing webhook by id. This is the only mutation on an existing webhook; `url`, `type`, `events` and `headers` are immutable (delete and recreate to change them).\n\nRe-enabling a version 2 webhook that was auto-disabled also resets its consecutive failure count, so it gets a full run of deliveries before it can be auto-disabled again.\n\n#### Path parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `id` | string | Yes | Webhook id (24-hex). |\n\n#### Request body parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `enabled` | boolean | Yes | `true` to enable, `false` to disable. |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `webhook` | object | The updated webhook (`id`, `api_version`, `type`, `url`, `version`, `threshold`, `events`, `https_only`, `header_names`, `enabled`, `auto_disabled`). |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid webhook ID format` | 400 | `id` is not a valid 24-hex id. |\n| `Missing required field: enabled` | 400 | `enabled` not supplied. |\n| `Webhook not found` | 400 | No such webhook on this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{\n  \"enabled\": false\n}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"webhook\": {\n    \"id\": \"683554c596937cc4b90f5cf7\",\n    \"api_version\": 1,\n    \"type\": \"sms_events\",\n    \"url\": \"https://example.com/dlr\",\n    \"version\": 1,\n    \"threshold\": null,\n    \"events\": [],\n    \"https_only\": false,\n    \"header_names\": [],\n    \"enabled\": false,\n    \"auto_disabled\": false\n  },\n  \"message\": \"Webhook disabled\"\n}"
            },
            {
              "name": "400 Bad Request",
              "code": 400,
              "status": "Bad Request",
              "body": "{\n  \"error\": \"Webhook not found\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        },
        {
          "name": "Delete Webhook",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url_sms}}/webhooks/:id",
              "host": [
                "{{base_url_sms}}"
              ],
              "path": [
                "webhooks",
                ":id"
              ],
              "variable": [
                {
                  "key": "id",
                  "value": "",
                  "description": "Webhook id (24-hex)."
                }
              ]
            },
            "description": "Delete a webhook configuration by id.\n\nDeleting a version 2 webhook destroys its signing secret. There is no way to recover it; a replacement webhook gets a new secret.\n\n#### Path parameters\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `id` | string | Yes | Webhook id (24-hex). |\n\n#### Response fields\n| Field | Type | Description |\n| --- | --- | --- |\n| `error` | string | Empty on success. |\n| `message` | string | Confirmation message. |\n\n#### Errors\n| Error | HTTP | Cause |\n| --- | --- | --- |\n| `Invalid webhook ID format` | 400 | `id` is not a valid 24-hex id. |\n| `Webhook not found` | 400 | No such webhook on this account. |",
            "body": {
              "mode": "raw",
              "options": {
                "raw": {
                  "language": "json"
                }
              },
              "raw": "{}"
            }
          },
          "response": [
            {
              "name": "200 OK",
              "code": 200,
              "status": "OK",
              "body": "{\n  \"error\": \"\",\n  \"message\": \"Webhook deleted\"\n}"
            },
            {
              "name": "401 Unauthorized",
              "code": 401,
              "status": "Unauthorized",
              "body": "{\n  \"error\": \"Failed (Invalid API ID or Key)\"\n}"
            }
          ]
        }
      ]
    }
  ]
}
