{
    "openapi": "3.1.0",
    "info": {
        "title": "SMS Gateway API",
        "version": "1.0.0",
        "summary": "Send SMS and MMS through your own Android handsets.",
        "description": "Every send becomes a campaign, so it can be paused, stopped or re-timed afterwards. Nothing is delivered synchronously: a request queues work and a phone picks it up on its next poll, within about ten seconds. Subscribe to webhooks rather than polling — the `webhooks` section of this document describes every event and its payload.\n\n**Authentication.** `Authorization: Bearer <key>` on every request.\n\n**Errors.** One shape, always: `{\"error\": {\"type\", \"code\", \"message\", \"param\"}, \"requestId\"}`. Branch on the status code first and `code` second; `message` is a sentence for a human and may be reworded.\n\n**Pagination.** Cursor, not offset — pass the previous page's `nextCursor` as `startingAfter`. The message log is written to constantly and an offset walk over it returns rows twice and skips others.\n\n**Times.** ISO-8601 UTC everywhere, both directions. A timestamp you send without an offset is read as UTC: an API request has no viewer timezone.\n\n**Writing a store integration.** Four things carry most of the weight, and each is documented on the route that takes it. Send an `Idempotency-Key` on every write, because an order hook fires twice more often than anybody expects. Put your own order reference in `metadata` on the campaign, and it is copied onto every webhook that campaign produces, so you never have to store our ids to know which order an event belongs to. Pass `defaultCountry` and a local number typed into a checkout form is read the way the shopper meant it. And price the send with `POST /messages/preview` before you take the order, rather than assuming one message is one credit — on most installations a long message is charged per part.\n\n**Retention.** History on this installation is a window, not an archive. Sent, delivered and received messages are kept for at least 30 days; anything still unsent, failed or cancelled for at least 45 days. A campaign is kept for at least 20 days, and goes once its messages have. USSD history is kept for at least 330 days. A `GET` for an id that has aged out answers **404** like any other missing resource, with `code` `message_expired`, `campaign_expired` or `ussd_expired` rather than `not_found` — branch on that if you reconcile our ids against your own, and treat it as \"we deleted this\" rather than \"you asked for the wrong thing\". `type` is `not_found` either way and does not change.\n\n`scheduleAt` is refused beyond **38 days** ahead, on `POST /campaigns` and `PATCH /campaigns/{id}`: a message scheduled past the window that holds it would be removed before it could send.",
        "contact": {
            "name": "SMS Gateway",
            "url": "https://sms-gateway.app"
        },
        "license": {
            "name": "Proprietary"
        }
    },
    "servers": [
        {
            "url": "https://app.sms-gateway.app/api/v1"
        }
    ],
    "security": [
        {
            "bearerAuth": []
        }
    ],
    "tags": [
        {
            "name": "Meta",
            "description": ""
        },
        {
            "name": "Messages",
            "description": "Send, and read back what happened."
        },
        {
            "name": "Campaigns",
            "description": "A send as a unit — pause it, stop it, or re-time it before it fires."
        },
        {
            "name": "Devices",
            "description": "The handsets doing the sending. Read-only: a device arrives by pairing the app, never through this API."
        },
        {
            "name": "Contacts",
            "description": "Lists and the people on them. Unsubscribe is a state, not a deletion."
        },
        {
            "name": "Blacklist",
            "description": "The account-wide never-send list. Every send is already filtered against it; this is how an integration writes to it."
        },
        {
            "name": "Templates",
            "description": "The message bodies the customer wrote in the panel, so an integration does not ask them to write the same text twice."
        },
        {
            "name": "USSD",
            "description": "Dial a network code on a handset and read the answer."
        },
        {
            "name": "Account",
            "description": "Credits, limits, and which key is asking."
        },
        {
            "name": "Webhooks",
            "description": "Manage your own endpoints. The events they receive are in the `webhooks` section of this document."
        }
    ],
    "externalDocs": {
        "description": "Guides, the store plugins, and the change log.",
        "url": "https://docs.sms-gateway.app"
    },
    "paths": {
        "/openapi.json": {
            "get": {
                "operationId": "getSpec",
                "summary": "This document",
                "tags": [
                    "Meta"
                ],
                "description": "OpenAPI 3.1, generated from the same registry the router dispatches on, so it cannot describe an endpoint that does not exist. Includes the webhook event catalogue under the top-level `webhooks` key.",
                "responses": {
                    "200": {
                        "description": "Success.",
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    }
                },
                "security": [],
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/openapi.json\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/openapi.json\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/openapi.json\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/openapi.json\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/messages": {
            "post": {
                "operationId": "sendMessage",
                "summary": "Send one or more messages",
                "tags": [
                    "Messages"
                ],
                "description": "Queues messages for sending and returns immediately. Nothing has left the device when this responds — a phone picks the batch up on its next poll, within about ten seconds. Every send becomes a campaign, so it can be paused, stopped or moved to another device afterwards — unless you send a single message with `campaign: false`.\n\n**A refusal is a `400` whose `error.code` says which rule stopped it**, so an integration can act on it without reading the sentence: `insufficient_credits`, `no_active_device`, `subscription_expired`, `invalid_schedule`, `sim_not_present` or `zero_messages`. Anything else is `send_failed`. **A refusal writes nothing** — no message row, no id, no credit spent — so the retry after a top-up is this request again with a fresh `Idempotency-Key`, not `POST /messages/{id}/resend`.",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "maxLength": 255,
                            "example": "3f1b0c8a-9d2e-4c77-9f5a-2b6d1e0f4a83"
                        },
                        "description": "Optional, and every integration should send one. A retry carrying the key of a request that already succeeded is answered with that request's response — `Idempotency-Replayed: true` — instead of doing the work again. Keys are scoped to the account, remembered for 24 hours, and belong to exactly one request: reusing one with a different body is `409 idempotency_key_reused`, and reusing one while the first request is still running is `409 request_in_progress`. A failed request releases its key, so a corrected retry may keep it. Any printable string up to 255 characters; a UUID is ideal."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "to": {
                                        "type": "array",
                                        "description": "Recipient numbers in E.164. For an MMS addressed to several handsets at once, join them with `|` inside one entry.",
                                        "example": [
                                            "+11234567890",
                                            "+11234567891"
                                        ],
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "text": {
                                        "type": "string",
                                        "description": "The message body. Required unless this is an `mms` carrying an attachment. Supports spintax — `{Hi|Hello}` — and `%random-4%`.",
                                        "example": "Your code is %random-6%"
                                    },
                                    "type": {
                                        "type": "string",
                                        "description": "Message type.",
                                        "enum": [
                                            "sms",
                                            "mms"
                                        ],
                                        "default": "sms"
                                    },
                                    "attachments": {
                                        "type": "array",
                                        "description": "URLs the device fetches. `mms` only; the phone downloads these, so they must be reachable from the handset's network.",
                                        "example": [
                                            "https://example.com/poster.jpg"
                                        ],
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "deviceIds": {
                                        "type": "array",
                                        "description": "Which phones to split the batch across. Omitted, the account's primary device takes all of it.",
                                        "example": [
                                            3
                                        ],
                                        "items": {
                                            "type": "integer"
                                        }
                                    },
                                    "simSlot": {
                                        "type": "integer",
                                        "description": "SIM slot on each named device. Omitted, the device chooses."
                                    },
                                    "prioritize": {
                                        "type": "boolean",
                                        "description": "Jump this batch ahead of anything already queued on the device.",
                                        "default": false
                                    },
                                    "scheduleAt": {
                                        "type": "string",
                                        "description": "Send later. ISO-8601; **UTC unless the string carries an offset** — an API request has no viewer timezone to fall back on.",
                                        "format": "date-time",
                                        "example": "2026-09-01T09:00:00Z"
                                    },
                                    "campaignName": {
                                        "type": "string",
                                        "description": "What to call this send in the panel. Omitted, the first line of the message is used."
                                    },
                                    "metadata": {
                                        "type": "object",
                                        "description": "Your own reference for this send — an order id, a cart token, whatever lets you recognise it later. Returned on the campaign and copied onto every `message.sent`, `message.delivered` and `message.failed` webhook this send produces, which is what lets a receiver say **which order** an event was about without keeping its own table of our ids. A flat object of up to 50 string, number or boolean values, 16 KB in total. We never read it, index it or filter on it.",
                                        "example": {
                                            "orderId": "1234",
                                            "source": "woocommerce"
                                        }
                                    },
                                    "defaultCountry": {
                                        "type": "string",
                                        "description": "Two-letter ISO-3166 code. Given one, a recipient written in local form — `07700900123` — is rewritten to E.164 before it is sent. **Omitted, nothing is rewritten and nothing is loaded**: your numbers reach the handset exactly as you spelled them, which is what has always happened and is why this is opt-in. A number that cannot be rewritten is passed through unchanged rather than refused, so setting this can never make a send fail that would otherwise have worked.",
                                        "example": "GB"
                                    },
                                    "dryRun": {
                                        "type": "boolean",
                                        "description": "Validate, price and check everything — the blacklist, your credit balance, the devices, the schedule — then send nothing. Answers **200** rather than 202, with the same body shape and every `id` null. Nothing is minted, nothing is charged and no phone is woken. This is what a plugin's *Test configuration* button should call, and it is what a reviewer pressing Send in a sandbox should reach. **A dry run never consumes an `Idempotency-Key`**, so the real send that follows with the same key still sends.",
                                        "default": false
                                    },
                                    "dedupe": {
                                        "type": "boolean",
                                        "description": "Send once to a number that appears more than once in `to`. Off by default, and deliberately: a repeated recipient has always cost a credit and counted towards `accepted`, and changing that for callers who did not ask would change a number some of them reconcile against their own records. Numbers are compared exactly as you spelled them — `+15551234` and `15551234` are not collapsed.",
                                        "default": false
                                    },
                                    "campaign": {
                                        "type": "boolean",
                                        "description": "Set `false` to send without creating a campaign. **One recipient only** — more than one is refused, because a campaign is the only thing that can pause, stop or re-target the rest of a batch part way through, and a batch with no way to stop it is not something we will mint for you by accident.\n\nThe message is queued, charged, sent and reported exactly as any other. What changes is that it never appears on the Campaigns page, `campaignId` comes back `null` here and on every webhook the send produces, and there is nothing for `POST /campaigns/{id}/stop` to address — withdraw it with `DELETE /messages/{id}` instead. This is what the panel's own Quick Send has always done. `campaignName` and `metadata` are refused alongside it, having nowhere left to live.",
                                        "default": true
                                    }
                                },
                                "required": [
                                    "to"
                                ]
                            },
                            "example": {
                                "to": [
                                    "+11234567890",
                                    "+11234567891"
                                ],
                                "text": "Your code is %random-6%",
                                "attachments": [
                                    "https://example.com/poster.jpg"
                                ],
                                "deviceIds": [
                                    3
                                ],
                                "scheduleAt": "2026-09-01T09:00:00Z",
                                "metadata": {
                                    "orderId": "1234",
                                    "source": "woocommerce"
                                },
                                "defaultCountry": "GB"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/MessageBatch"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        }
                    },
                    "202": {
                        "description": "Accepted. Queued, not yet sent — a device collects it on its next poll.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/MessageBatch"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "403": {
                        "description": "A real key that is not allowed to do this — most often an expired subscription.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/messages\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"to\":[\"+11234567890\",\"+11234567891\"],\"text\":\"Your code is %random-6%\",\"attachments\":[\"https://example.com/poster.jpg\"],\"deviceIds\":[3],\"scheduleAt\":\"2026-09-01T09:00:00Z\",\"metadata\":{\"orderId\":\"1234\",\"source\":\"woocommerce\"},\"defaultCountry\":\"GB\"}'"
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/messages\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Idempotency-Key: \" . bin2hex(random_bytes(16)),\n        \"Content-Type: application/json\",\n    ],\n    CURLOPT_POSTFIELDS     => json_encode([\n        \"to\" => [\"+11234567890\", \"+11234567891\"],\n        \"text\" => \"Your code is %random-6%\",\n        \"attachments\" => [\"https://example.com/poster.jpg\"],\n        \"deviceIds\" => [3],\n        \"scheduleAt\" => \"2026-09-01T09:00:00Z\",\n        \"metadata\" => [\n            \"orderId\" => \"1234\",\n            \"source\" => \"woocommerce\",\n        ],\n        \"defaultCountry\" => \"GB\",\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/messages\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    to: [\"+11234567890\", \"+11234567891\"],\n    text: \"Your code is %random-6%\",\n    attachments: [\"https://example.com/poster.jpg\"],\n    deviceIds: [3],\n    scheduleAt: \"2026-09-01T09:00:00Z\",\n    metadata: {\n      orderId: \"1234\",\n      source: \"woocommerce\",\n    },\n    defaultCountry: \"GB\",\n  }),\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import uuid\nimport requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/messages\",\n    headers={\n        \"Authorization\": \"Bearer sboxk_live_your_key_here\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json={\n        \"to\": [\"+11234567890\", \"+11234567891\"],\n        \"text\": \"Your code is %random-6%\",\n        \"attachments\": [\"https://example.com/poster.jpg\"],\n        \"deviceIds\": [3],\n        \"scheduleAt\": \"2026-09-01T09:00:00Z\",\n        \"metadata\": {\n            \"orderId\": \"1234\",\n            \"source\": \"woocommerce\",\n        },\n        \"defaultCountry\": \"GB\",\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "get": {
                "operationId": "listMessages",
                "summary": "List messages",
                "tags": [
                    "Messages"
                ],
                "description": "Newest first, cursor paginated. Cursor rather than offset because this table is written to constantly — an offset walk over it returns rows twice and skips others.\n\n**A text search has to be given a date range.** `search` and `numberContains` are contains-matches, which no index can seek into: without a range they read every message the account has ever had, which is seconds per request on a busy one. Both require `sentAfter`, and the range may span at most 90 days — page through a longer history a window at a time. Every other filter is unbounded and cheap.\n\n**On an install that prunes, this answers honestly with fewer rows.** A date range older than the retention window returns what survives it, not an error.",
                "parameters": [
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "Pending",
                                "Queued",
                                "Scheduled",
                                "Sent",
                                "Delivered",
                                "Failed",
                                "Canceled",
                                "Received"
                            ]
                        },
                        "description": "Only messages in this status."
                    },
                    {
                        "name": "deviceId",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "example": "12"
                        },
                        "description": "Only messages on this device. Pass `none` for messages that have not been handed to a phone at all — an API send still waiting for a handset to poll, and anything sent before a device was paired."
                    },
                    {
                        "name": "campaignId",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Only messages in this campaign."
                    },
                    {
                        "name": "number",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Exact recipient match."
                    },
                    {
                        "name": "numberContains",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "example": "1234"
                        },
                        "description": "Recipient contains this. Requires `sentAfter`; see below."
                    },
                    {
                        "name": "search",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "example": "invoice"
                        },
                        "description": "Message body contains this. Requires `sentAfter`; see below."
                    },
                    {
                        "name": "type",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "sms",
                                "mms"
                            ]
                        },
                        "description": "Only messages of this kind."
                    },
                    {
                        "name": "groupId",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "One device's shard of a campaign. Narrower than `campaignId`, and the right question when a single phone is behaving oddly."
                    },
                    {
                        "name": "sentSimId",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The SIM card that actually carried the message, as the handset reported it — so a send that asked for SIM 1 and failed over to SIM 2 is found under the card that really sent it, and a card since pulled still answers for its own traffic. Messages sent before the reporting app existed have no card recorded and are in no SIM selection."
                    },
                    {
                        "name": "memberId",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Who sent it. **`0` is the account holder**, not an id — a message sent by the account itself has no team member against it, and an empty value already means \"no filter\"."
                    },
                    {
                        "name": "sentAfter",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        },
                        "description": "Queued at or after this time."
                    },
                    {
                        "name": "sentBefore",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        },
                        "description": "Queued at or before this time."
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "minimum": 1,
                            "maximum": 200
                        },
                        "description": "Page size."
                    },
                    {
                        "name": "startingAfter",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The `nextCursor` from the previous page."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/MessagePage"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/messages?deviceId=12&numberContains=1234&search=invoice\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/messages?deviceId=12&numberContains=1234&search=invoice\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/messages?deviceId=12&numberContains=1234&search=invoice\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/messages?deviceId=12&numberContains=1234&search=invoice\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/messages/preview": {
            "post": {
                "operationId": "previewMessage",
                "summary": "Price a send without sending it",
                "tags": [
                    "Messages"
                ],
                "description": "Takes the body of `POST /messages` and answers what it would cost: how many recipients survive your blacklist, how many credits that is, which alphabet the text forces and how many parts each message becomes. **Nothing is queued, charged or sent.**\n\nTwo of those numbers you cannot work out yourself. `suppressed` needs the blacklist, which is yours and which no endpoint publishes. `billableCredits` comes from the same function the send charges with, so on an install that bills per message part it is the per-part price rather than the recipient count.\n\nA POST because ten thousand recipients do not fit in a query string; it needs the `write` scope for the same reason, and is still a read — it writes nothing.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "to": {
                                        "type": "array",
                                        "description": "The same recipients you would send to.",
                                        "example": [
                                            "+11234567890"
                                        ],
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "text": {
                                        "type": "string",
                                        "description": "The same body. Spintax and `%random-n%` are counted as written — they expand per recipient, so a spun message's real part count can vary and this is the count for the template.",
                                        "example": "Your order has shipped."
                                    },
                                    "type": {
                                        "type": "string",
                                        "enum": [
                                            "sms",
                                            "mms"
                                        ],
                                        "default": "sms"
                                    },
                                    "attachments": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "deviceIds": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer"
                                        }
                                    },
                                    "simSlot": {
                                        "type": "integer"
                                    },
                                    "prioritize": {
                                        "type": "boolean",
                                        "default": false
                                    },
                                    "scheduleAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "defaultCountry": {
                                        "type": "string",
                                        "description": "As on `POST /messages`.",
                                        "example": "GB"
                                    }
                                },
                                "required": [
                                    "to"
                                ]
                            },
                            "example": {
                                "to": [
                                    "+11234567890"
                                ],
                                "text": "Your order has shipped.",
                                "defaultCountry": "GB"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/MessagePreview"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "403": {
                        "description": "A real key that is not allowed to do this — most often an expired subscription.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/messages/preview\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"to\":[\"+11234567890\"],\"text\":\"Your order has shipped.\",\"defaultCountry\":\"GB\"}'"
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/messages/preview\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Content-Type: application/json\",\n    ],\n    CURLOPT_POSTFIELDS     => json_encode([\n        \"to\" => [\"+11234567890\"],\n        \"text\" => \"Your order has shipped.\",\n        \"defaultCountry\" => \"GB\",\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/messages/preview\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    to: [\"+11234567890\"],\n    text: \"Your order has shipped.\",\n    defaultCountry: \"GB\",\n  }),\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/messages/preview\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n    json={\n        \"to\": [\"+11234567890\"],\n        \"text\": \"Your order has shipped.\",\n        \"defaultCountry\": \"GB\",\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/messages/count": {
            "get": {
                "operationId": "countMessages",
                "summary": "Count messages",
                "tags": [
                    "Messages"
                ],
                "description": "How many messages match — the same filters `GET /messages` takes, and the number the list deliberately does not carry. A cursor page has no total because counting the whole set on every page is work nobody asked for after the first one; ask for it here, once.\n\n**The count is capped.** No index makes counting everything cheaper than counting everything, so beyond 100,000 this stops and answers `exact: false` with the cap. Narrow the range if you need the true figure for a set that large.",
                "parameters": [
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "Pending",
                                "Queued",
                                "Scheduled",
                                "Sent",
                                "Delivered",
                                "Failed",
                                "Canceled",
                                "Received"
                            ]
                        },
                        "description": "Only messages in this status."
                    },
                    {
                        "name": "deviceId",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "example": "12"
                        },
                        "description": "Only messages on this device. Pass `none` for messages that have not been handed to a phone at all — an API send still waiting for a handset to poll, and anything sent before a device was paired."
                    },
                    {
                        "name": "campaignId",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Only messages in this campaign."
                    },
                    {
                        "name": "number",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Exact recipient match."
                    },
                    {
                        "name": "numberContains",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "example": "1234"
                        },
                        "description": "Recipient contains this. Requires `sentAfter`; see below."
                    },
                    {
                        "name": "search",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "example": "invoice"
                        },
                        "description": "Message body contains this. Requires `sentAfter`; see below."
                    },
                    {
                        "name": "type",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "sms",
                                "mms"
                            ]
                        },
                        "description": "Only messages of this kind."
                    },
                    {
                        "name": "groupId",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "One device's shard of a campaign. Narrower than `campaignId`, and the right question when a single phone is behaving oddly."
                    },
                    {
                        "name": "sentSimId",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The SIM card that actually carried the message, as the handset reported it — so a send that asked for SIM 1 and failed over to SIM 2 is found under the card that really sent it, and a card since pulled still answers for its own traffic. Messages sent before the reporting app existed have no card recorded and are in no SIM selection."
                    },
                    {
                        "name": "memberId",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Who sent it. **`0` is the account holder**, not an id — a message sent by the account itself has no team member against it, and an empty value already means \"no filter\"."
                    },
                    {
                        "name": "sentAfter",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        },
                        "description": "Queued at or after this time."
                    },
                    {
                        "name": "sentBefore",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date-time"
                        },
                        "description": "Queued at or before this time."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/MessageCount"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/messages/count?deviceId=12&numberContains=1234&search=invoice\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/messages/count?deviceId=12&numberContains=1234&search=invoice\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/messages/count?deviceId=12&numberContains=1234&search=invoice\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/messages/count?deviceId=12&numberContains=1234&search=invoice\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/messages/{id}": {
            "get": {
                "operationId": "getMessage",
                "summary": "Retrieve a message",
                "tags": [
                    "Messages"
                ],
                "description": "The current state of one message, including the device's result code once it has reported.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The message's id."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Message"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/messages/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/messages/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/messages/123\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/messages/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "delete": {
                "operationId": "cancelMessage",
                "summary": "Cancel a message that has not gone yet",
                "tags": [
                    "Messages"
                ],
                "description": "Withdraws one message. Until now the only cancel was stopping the whole campaign, so pulling a single order's notification meant stopping nine hundred other people's as well.\n\n**The message is not erased — it is marked `Canceled`,** which is exactly what stopping a campaign does to the same rows, and the response carries it so you can see the transition. Cancellable from `Pending`, `Queued` and `Scheduled` only; anything else is `409 not_cancellable`. `Queued` means a handset already has the message in hand, so a few of a batch may still send before the phone next polls — a late report cannot un-cancel the row.\n\n**Credits are not refunded.** They were spent when the send was accepted, and stopping a campaign does not refund either; cancelling a hundred messages one at a time must not cost less than cancelling the campaign holding them.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The message's id."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Message"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X DELETE \"https://app.sms-gateway.app/api/v1/messages/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/messages/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"DELETE\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/messages/123\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.delete(\n    \"https://app.sms-gateway.app/api/v1/messages/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/messages/{id}/resend": {
            "post": {
                "operationId": "resendMessage",
                "summary": "Resend a message",
                "tags": [
                    "Messages"
                ],
                "description": "Queues the same text to the same number again. This re-points the existing message rather than creating a second one — the id stays the same, `status` returns to `Pending` and the previous result is cleared — so a caller polling that id keeps polling the right thing. If the message belongs to a campaign that had finished, the campaign reopens, because work in flight has to stay under a campaign whose Pause and Stop still apply. Costs a credit. Only a message that has finished can be resent; one still queued would become two messages to the same person.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The message's id."
                    },
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "maxLength": 255,
                            "example": "3f1b0c8a-9d2e-4c77-9f5a-2b6d1e0f4a83"
                        },
                        "description": "Optional, and every integration should send one. A retry carrying the key of a request that already succeeded is answered with that request's response — `Idempotency-Replayed: true` — instead of doing the work again. Keys are scoped to the account, remembered for 24 hours, and belong to exactly one request: reusing one with a different body is `409 idempotency_key_reused`, and reusing one while the first request is still running is `409 request_in_progress`. A failed request releases its key, so a corrected retry may keep it. Any printable string up to 255 characters; a UUID is ideal."
                    }
                ],
                "responses": {
                    "202": {
                        "description": "Accepted. Queued, not yet sent — a device collects it on its next poll.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Message"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/messages/123/resend\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Idempotency-Key: $(uuidgen)\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/messages/123/resend\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Idempotency-Key: \" . bin2hex(random_bytes(16)),\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/messages/123/resend\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import uuid\nimport requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/messages/123/resend\",\n    headers={\n        \"Authorization\": \"Bearer sboxk_live_your_key_here\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/campaigns": {
            "get": {
                "operationId": "listCampaigns",
                "summary": "List campaigns",
                "tags": [
                    "Campaigns"
                ],
                "description": "Newest first, cursor paginated. Each row carries its progress counts, computed for the whole page in one aggregate.",
                "parameters": [
                    {
                        "name": "state",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "Draft",
                                "Expanding",
                                "Scheduled",
                                "Running",
                                "Paused",
                                "Completed",
                                "Stopped"
                            ]
                        }
                    },
                    {
                        "name": "source",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "numbers",
                                "contacts",
                                "spreadsheet",
                                "api",
                                "resend"
                            ]
                        },
                        "description": "`api` is a send through `POST /messages`."
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "minimum": 1,
                            "maximum": 200
                        },
                        "description": "Page size."
                    },
                    {
                        "name": "startingAfter",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The `nextCursor` from the previous page."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/CampaignPage"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/campaigns\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/campaigns\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/campaigns\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/campaigns\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "post": {
                "operationId": "createCampaign",
                "summary": "Send to contact lists",
                "tags": [
                    "Campaigns"
                ],
                "description": "The list-driven send. `POST /messages` takes numbers; this takes lists somebody curated, resolves `%name%` and the other tokens per contact, skips anyone unsubscribed, and deduplicates a person who appears on two of the named lists. Set `scheduleAt` and it waits; leave it out and devices start picking it up on their next poll.\n\n**The 202 comes back before the messages exist.** A set of contact lists has no size limit, so the campaign is returned in `Expanding` and a worker writes its messages — usually within a second, and a hundred thousand recipients in a few. What is decided while you hold the request is everything you could act on: the lists are yours, the devices are yours, the schedule is in the future, and `accepted` recipients fit in your credits. Poll `GET /campaigns/{id}` until `state` leaves `Expanding`; the transition endpoints refuse a campaign that is still expanding, because pausing rows nobody has finished writing is a race.",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "maxLength": 255,
                            "example": "3f1b0c8a-9d2e-4c77-9f5a-2b6d1e0f4a83"
                        },
                        "description": "Optional, and every integration should send one. A retry carrying the key of a request that already succeeded is answered with that request's response — `Idempotency-Replayed: true` — instead of doing the work again. Keys are scoped to the account, remembered for 24 hours, and belong to exactly one request: reusing one with a different body is `409 idempotency_key_reused`, and reusing one while the first request is still running is `409 request_in_progress`. A failed request releases its key, so a corrected retry may keep it. Any printable string up to 255 characters; a UUID is ideal."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "listIds": {
                                        "type": "array",
                                        "description": "Contact lists to send to.",
                                        "example": [
                                            4
                                        ],
                                        "items": {
                                            "type": "integer"
                                        }
                                    },
                                    "text": {
                                        "type": "string",
                                        "description": "The body. Required unless this is an `mms` carrying an attachment. Supports `%name%`, `%number%`, `%listID%`, spintax — `{Hi|Hello}` — and `%random-4%`.",
                                        "example": "Hi %name%, your order has shipped."
                                    },
                                    "name": {
                                        "type": "string",
                                        "description": "What to call this send in the panel."
                                    },
                                    "type": {
                                        "type": "string",
                                        "enum": [
                                            "sms",
                                            "mms"
                                        ],
                                        "default": "sms"
                                    },
                                    "attachments": {
                                        "type": "array",
                                        "description": "`mms` only.",
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "deviceIds": {
                                        "type": "array",
                                        "items": {
                                            "type": "integer"
                                        }
                                    },
                                    "simSlot": {
                                        "type": "integer"
                                    },
                                    "prioritize": {
                                        "type": "boolean",
                                        "default": false
                                    },
                                    "scheduleAt": {
                                        "type": "string",
                                        "description": "ISO-8601; UTC unless the string carries an offset.",
                                        "format": "date-time"
                                    },
                                    "metadata": {
                                        "type": "object",
                                        "description": "Your own reference for this send. Returned on the campaign and copied onto every message webhook it produces, so a receiver can say which of its own records an event belongs to. A flat object of up to 50 string, number or boolean values, 16 KB in total. We never read it.",
                                        "example": {
                                            "campaign": "october-promo"
                                        }
                                    },
                                    "dryRun": {
                                        "type": "boolean",
                                        "description": "Resolve the lists, count the recipients, price them and check your credits, devices and schedule — then queue nothing. Answers **200** with `id: null` and `estimatedCredits`. This is the cheap way to find out that a 40,000-contact campaign is 8,000 credits short before it is already expanding. **Never consumes an `Idempotency-Key`.**",
                                        "default": false
                                    }
                                },
                                "required": [
                                    "listIds"
                                ]
                            },
                            "example": {
                                "listIds": [
                                    4
                                ],
                                "text": "Hi %name%, your order has shipped.",
                                "metadata": {
                                    "campaign": "october-promo"
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Campaign"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        }
                    },
                    "202": {
                        "description": "Accepted. Queued, not yet sent — a device collects it on its next poll.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Campaign"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/campaigns\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"listIds\":[4],\"text\":\"Hi %name%, your order has shipped.\",\"metadata\":{\"campaign\":\"october-promo\"}}'"
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/campaigns\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Idempotency-Key: \" . bin2hex(random_bytes(16)),\n        \"Content-Type: application/json\",\n    ],\n    CURLOPT_POSTFIELDS     => json_encode([\n        \"listIds\" => [4],\n        \"text\" => \"Hi %name%, your order has shipped.\",\n        \"metadata\" => [\n            \"campaign\" => \"october-promo\",\n        ],\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/campaigns\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    listIds: [4],\n    text: \"Hi %name%, your order has shipped.\",\n    metadata: {\n      campaign: \"october-promo\",\n    },\n  }),\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import uuid\nimport requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/campaigns\",\n    headers={\n        \"Authorization\": \"Bearer sboxk_live_your_key_here\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json={\n        \"listIds\": [4],\n        \"text\": \"Hi %name%, your order has shipped.\",\n        \"metadata\": {\n            \"campaign\": \"october-promo\",\n        },\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/campaigns/{id}": {
            "get": {
                "operationId": "getCampaign",
                "summary": "Retrieve a campaign",
                "tags": [
                    "Campaigns"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The campaign's id."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Campaign"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/campaigns/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/campaigns/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/campaigns/123\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/campaigns/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "patch": {
                "operationId": "updateCampaign",
                "summary": "Rename or re-time a campaign",
                "tags": [
                    "Campaigns"
                ],
                "description": "The message rows already exist — they are minted when the campaign is created, which is what lets a scheduled send fire with nobody logged in. So the body, the recipients and the device split cannot be changed here. Renaming and `metadata` work at any time; `scheduleAt` and `prioritize` work while the campaign is still `Scheduled`.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The campaign's id."
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "metadata": {
                                        "type": "object",
                                        "description": "Replaces your reference on this campaign, whole — it is not merged key by key. Send `{}` to clear it. Same 50 keys and 16 KB as on create. Messages already sent carry the value the campaign had when the event was emitted; changing it here does not rewrite them.",
                                        "example": {
                                            "orderId": "1234"
                                        }
                                    },
                                    "scheduleAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "prioritize": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": {
                                "metadata": {
                                    "orderId": "1234"
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Campaign"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X PATCH \"https://app.sms-gateway.app/api/v1/campaigns/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"metadata\":{\"orderId\":\"1234\"}}'"
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/campaigns/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"PATCH\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Content-Type: application/json\",\n    ],\n    CURLOPT_POSTFIELDS     => json_encode([\n        \"metadata\" => [\n            \"orderId\" => \"1234\",\n        ],\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/campaigns/123\", {\n  method: \"PATCH\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    metadata: {\n      orderId: \"1234\",\n    },\n  }),\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.patch(\n    \"https://app.sms-gateway.app/api/v1/campaigns/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n    json={\n        \"metadata\": {\n            \"orderId\": \"1234\",\n        },\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/campaigns/{id}/pause": {
            "post": {
                "operationId": "pauseCampaign",
                "summary": "Pause a campaign",
                "tags": [
                    "Campaigns"
                ],
                "description": "Withholds the remaining work from devices. Nothing is cancelled and nothing is told to the handsets — they simply have nothing to collect.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The campaign's id."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Campaign"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/campaigns/123/pause\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/campaigns/123/pause\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/campaigns/123/pause\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/campaigns/123/pause\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/campaigns/{id}/resume": {
            "post": {
                "operationId": "resumeCampaign",
                "summary": "Resume a campaign",
                "tags": [
                    "Campaigns"
                ],
                "description": "Offers the work again; the next poll picks it up. A campaign paused while `Scheduled` resumes as `Running` even if its moment has passed — the intent was to send it, not to wait for a time that is gone.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The campaign's id."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Campaign"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/campaigns/123/resume\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/campaigns/123/resume\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/campaigns/123/resume\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/campaigns/123/resume\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/campaigns/{id}/stop": {
            "post": {
                "operationId": "stopCampaign",
                "summary": "Stop a campaign",
                "tags": [
                    "Campaigns"
                ],
                "description": "Cancels everything not yet reported, including messages a device already holds. A handset may still send a few before it notices. Terminal — there is no resume from here.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The campaign's id."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Campaign"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/campaigns/123/stop\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/campaigns/123/stop\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/campaigns/123/stop\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/campaigns/123/stop\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/devices": {
            "get": {
                "operationId": "listDevices",
                "summary": "List devices",
                "tags": [
                    "Devices"
                ],
                "description": "Every handset this account can send from, including ones shared with it. Devices are created by pairing the app, never through this API.",
                "parameters": [
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "online",
                                "idle",
                                "offline",
                                "never"
                            ]
                        },
                        "description": "Derived from the heartbeat, not stored."
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "minimum": 1,
                            "maximum": 200
                        },
                        "description": "Page size."
                    },
                    {
                        "name": "startingAfter",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The `nextCursor` from the previous page."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/DevicePage"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/devices\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/devices\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/devices\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/devices\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/devices/{id}": {
            "get": {
                "operationId": "getDevice",
                "summary": "Retrieve a device",
                "tags": [
                    "Devices"
                ],
                "description": "With its SIMs — which is where the `simSlot` numbers a send uses come from.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The device's id."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Device"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/devices/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/devices/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/devices/123\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/devices/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "patch": {
                "operationId": "updateDevice",
                "summary": "Rename a device",
                "tags": [
                    "Devices"
                ],
                "description": "The name THIS account gave the handset. A device shared between accounts is named separately by each of them, so this never changes what anybody else sees. Send an empty string to clear it and go back to the model name. Devices are created by pairing the app and cannot be created here.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The device's id."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "description": "At most 25 characters, counted as characters and not bytes.",
                                        "maxLength": 25
                                    }
                                },
                                "required": [
                                    "name"
                                ]
                            },
                            "example": {
                                "name": "…"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Device"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X PATCH \"https://app.sms-gateway.app/api/v1/devices/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"…\"}'"
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/devices/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"PATCH\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Content-Type: application/json\",\n    ],\n    CURLOPT_POSTFIELDS     => json_encode([\n        \"name\" => \"…\",\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/devices/123\", {\n  method: \"PATCH\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    name: \"…\",\n  }),\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.patch(\n    \"https://app.sms-gateway.app/api/v1/devices/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n    json={\n        \"name\": \"…\",\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "delete": {
                "operationId": "deleteDevice",
                "summary": "Unpair a device",
                "tags": [
                    "Devices"
                ],
                "description": "Removes the handset from this account for good; the app has to scan the pairing code again to come back. **Only the owner may do this, and a device shared with any other account is refused with `409 device_shared`** — unpairing it would take the phone away from people this request cannot see. Stop sharing it in the panel first. Messages already sent from it are unaffected.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The device's id."
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X DELETE \"https://app.sms-gateway.app/api/v1/devices/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/devices/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"DELETE\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/devices/123\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.delete(\n    \"https://app.sms-gateway.app/api/v1/devices/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/devices/{id}/sims/{simId}": {
            "patch": {
                "operationId": "renameSim",
                "summary": "Rename a SIM card",
                "tags": [
                    "Devices"
                ],
                "description": "Your own name for the card, which outranks the carrier name everywhere the card is shown and is written onto every message it sends from now on — never onto messages it has already sent. **The name follows the CARD, not the tray**: move the SIM to another slot or another handset on this account and the name goes with it. Send an empty string or null to clear it. A card that has been taken out of the device is refused with `409 sim_retired`.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The device's id."
                    },
                    {
                        "name": "simId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The device's id."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "operatorLabel": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "description": "Empty or null clears it and the card reads as its carrier again. It is required: leaving it out is a 400 rather than a clear, so an empty body cannot erase a name somebody typed.",
                                        "maxLength": 64
                                    }
                                },
                                "required": [
                                    "operatorLabel"
                                ]
                            },
                            "example": {
                                "operatorLabel": "…"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SimRenamed"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X PATCH \"https://app.sms-gateway.app/api/v1/devices/123/sims/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"operatorLabel\":\"…\"}'"
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/devices/123/sims/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"PATCH\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Content-Type: application/json\",\n    ],\n    CURLOPT_POSTFIELDS     => json_encode([\n        \"operatorLabel\" => \"…\",\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/devices/123/sims/123\", {\n  method: \"PATCH\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    operatorLabel: \"…\",\n  }),\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.patch(\n    \"https://app.sms-gateway.app/api/v1/devices/123/sims/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n    json={\n        \"operatorLabel\": \"…\",\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/devices/pairing": {
            "post": {
                "operationId": "createDevicePairing",
                "summary": "Start pairing a device",
                "tags": [
                    "Devices"
                ],
                "description": "Mints a **single-use pairing code, good for 15 minutes**, and returns it as a QR image ready to show the person holding the phone. Point the SMS Gateway Android app's \"Scan pairing code\" at it and the handset joins this account.\n\nThis does **not** create a device — nothing can, from here. It creates an invitation: poll `GET /devices/pairing/{id}` until `status` is `claimed` and the handset is in `device`.\n\nShow `qr` (a PNG data URI, drop it straight into an `<img src>`) or render `payload` yourself, verbatim. `appUrl` is where the app is downloaded from, which is the step before scanning. One code pairs one phone; ask again for the next.",
                "responses": {
                    "201": {
                        "description": "Created.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/DevicePairing"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/devices/pairing\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/devices/pairing\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/devices/pairing\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/devices/pairing\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/devices/pairing/{id}": {
            "get": {
                "operationId": "getDevicePairing",
                "summary": "Check a pairing code",
                "tags": [
                    "Devices"
                ],
                "description": "Whether the phone has turned up yet. `pending` while the code is live and unused, `claimed` once a handset has paired — `device` is then the new device — and `expired` if the 15 minutes ran out first. Addressed by the invitation's id, never by the code itself: the code is a credential and does not belong in a URL.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The device's id."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/DevicePairingStatus"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/devices/pairing/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/devices/pairing/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/devices/pairing/123\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/devices/pairing/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "delete": {
                "operationId": "deleteDevicePairing",
                "summary": "Cancel a pairing code",
                "tags": [
                    "Devices"
                ],
                "description": "Kills a code before it expires — what to call when somebody closes the dialog it was drawn in, so it is not left scannable for the rest of its quarter of an hour. A code a handset has **already** used is refused with `409 pairing_claimed`: there is a device on the account now, and the verb for that is `DELETE /devices/{id}`.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The device's id."
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X DELETE \"https://app.sms-gateway.app/api/v1/devices/pairing/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/devices/pairing/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"DELETE\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/devices/pairing/123\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.delete(\n    \"https://app.sms-gateway.app/api/v1/devices/pairing/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/contact-lists": {
            "get": {
                "operationId": "listContactLists",
                "summary": "List contact lists",
                "tags": [
                    "Contacts"
                ],
                "parameters": [
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "minimum": 1,
                            "maximum": 200
                        },
                        "description": "Page size."
                    },
                    {
                        "name": "startingAfter",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The `nextCursor` from the previous page."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ContactListPage"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/contact-lists\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/contact-lists\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/contact-lists\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/contact-lists\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "post": {
                "operationId": "createContactList",
                "summary": "Create a contact list",
                "tags": [
                    "Contacts"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "example": "Newsletter"
                                    }
                                },
                                "required": [
                                    "name"
                                ]
                            },
                            "example": {
                                "name": "Newsletter"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ContactList"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/contact-lists\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Newsletter\"}'"
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/contact-lists\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Content-Type: application/json\",\n    ],\n    CURLOPT_POSTFIELDS     => json_encode([\n        \"name\" => \"Newsletter\",\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/contact-lists\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    name: \"Newsletter\",\n  }),\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/contact-lists\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n    json={\n        \"name\": \"Newsletter\",\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/contact-lists/{id}": {
            "delete": {
                "operationId": "deleteContactList",
                "summary": "Delete a contact list",
                "tags": [
                    "Contacts"
                ],
                "description": "The contacts in it go too. A contact with no list is a row nothing can reach.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The contact list's id."
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X DELETE \"https://app.sms-gateway.app/api/v1/contact-lists/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/contact-lists/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"DELETE\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/contact-lists/123\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.delete(\n    \"https://app.sms-gateway.app/api/v1/contact-lists/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/contacts": {
            "get": {
                "operationId": "listContacts",
                "summary": "List contacts",
                "tags": [
                    "Contacts"
                ],
                "parameters": [
                    {
                        "name": "listId",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "Only contacts in this list."
                    },
                    {
                        "name": "number",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Exact match."
                    },
                    {
                        "name": "subscribed",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "boolean"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "minimum": 1,
                            "maximum": 200
                        },
                        "description": "Page size."
                    },
                    {
                        "name": "startingAfter",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The `nextCursor` from the previous page."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ContactPage"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/contacts\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/contacts\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/contacts\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/contacts\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "post": {
                "operationId": "createContact",
                "summary": "Add a contact",
                "tags": [
                    "Contacts"
                ],
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "maxLength": 255,
                            "example": "3f1b0c8a-9d2e-4c77-9f5a-2b6d1e0f4a83"
                        },
                        "description": "Optional, and every integration should send one. A retry carrying the key of a request that already succeeded is answered with that request's response — `Idempotency-Replayed: true` — instead of doing the work again. Keys are scoped to the account, remembered for 24 hours, and belong to exactly one request: reusing one with a different body is `409 idempotency_key_reused`, and reusing one while the first request is still running is `409 request_in_progress`. A failed request releases its key, so a corrected retry may keep it. Any printable string up to 255 characters; a UUID is ideal."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "listId": {
                                        "type": "integer"
                                    },
                                    "number": {
                                        "type": "string",
                                        "example": "+11234567890"
                                    },
                                    "name": {
                                        "type": "string",
                                        "description": "Substituted for `%name%` when this contact is sent to."
                                    },
                                    "subscribed": {
                                        "type": "boolean",
                                        "default": true
                                    },
                                    "defaultCountry": {
                                        "type": "string",
                                        "description": "Two-letter ISO-3166 code. Given one, a number written in local form — `07700900123` — is rewritten to E.164 **before the list is checked for a duplicate**, which is what lets a store push the same customer every night without creating a second row for them each time.\n\n**Omitted, nothing is rewritten and nothing is loaded** — the number is stored exactly as you spelled it, which is what has always happened and is why this is opt-in.\n\n**It does not migrate what is already in the list.** Numbers stored before you started sending this are still in the form they arrived in, so the first sync after you turn it on can create one duplicate of an existing contact. Nothing rewrites a stored row on your behalf.",
                                        "example": "GB"
                                    }
                                },
                                "required": [
                                    "listId",
                                    "number"
                                ]
                            },
                            "example": {
                                "listId": 123,
                                "number": "+11234567890",
                                "defaultCountry": "GB"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Contact"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/contacts\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"listId\":123,\"number\":\"+11234567890\",\"defaultCountry\":\"GB\"}'"
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/contacts\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Idempotency-Key: \" . bin2hex(random_bytes(16)),\n        \"Content-Type: application/json\",\n    ],\n    CURLOPT_POSTFIELDS     => json_encode([\n        \"listId\" => 123,\n        \"number\" => \"+11234567890\",\n        \"defaultCountry\" => \"GB\",\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/contacts\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    listId: 123,\n    number: \"+11234567890\",\n    defaultCountry: \"GB\",\n  }),\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import uuid\nimport requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/contacts\",\n    headers={\n        \"Authorization\": \"Bearer sboxk_live_your_key_here\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json={\n        \"listId\": 123,\n        \"number\": \"+11234567890\",\n        \"defaultCountry\": \"GB\",\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/contacts/bulk": {
            "post": {
                "operationId": "bulkContacts",
                "summary": "Create or update up to 1000 contacts in one request",
                "tags": [
                    "Contacts"
                ],
                "description": "The endpoint a store sync needs. `POST /contacts` answers `409` for a number already in the list, so keeping one customer in step is three requests — create, take the 409, look the id up, patch — and twenty thousand customers is sixty thousand requests.\n\n**Every row is answered individually and the status is always 200.** One bad number does not discard the other 999: `results` carries the index you sent, the outcome (`created`, `updated`, `skipped` or `failed`) and either the contact id or the reason.\n\n**Re-posting a row that has not changed is `skipped` — no write, and no `contact.subscribed` / `contact.unsubscribed` event.** That is what makes a nightly full sync free: without it, pushing twenty thousand unchanged customers every night would emit twenty thousand opt-in events every night. The events fire on a genuine change to an existing contact, and never on a creation.\n\nA number repeated inside one request behaves as it would across two: the first entry creates, the second is a duplicate.\n\nIf the account's contact limit is reached partway through, the batch is truncated rather than refused: `truncated` comes back true and the rows beyond the limit are `failed` with `contacts_limit_reached`. Duplicates below the cut are still processed, because an unsubscribe matters most on a full account.\n\n`POST /contacts` is unchanged and is not deprecated.",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "maxLength": 255,
                            "example": "3f1b0c8a-9d2e-4c77-9f5a-2b6d1e0f4a83"
                        },
                        "description": "Optional, and every integration should send one. A retry carrying the key of a request that already succeeded is answered with that request's response — `Idempotency-Replayed: true` — instead of doing the work again. Keys are scoped to the account, remembered for 24 hours, and belong to exactly one request: reusing one with a different body is `409 idempotency_key_reused`, and reusing one while the first request is still running is `409 request_in_progress`. A failed request releases its key, so a corrected retry may keep it. Any printable string up to 255 characters; a UUID is ideal."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "listId": {
                                        "type": "integer"
                                    },
                                    "contacts": {
                                        "type": "array",
                                        "description": "One object per contact: `number` (required), `name`, `subscribed` (default true). At most 1000 — more than that is refused rather than truncated, because you can page it and a full account cannot.",
                                        "maxItems": 1000,
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "number": {
                                                    "type": "string",
                                                    "example": "+11234567890"
                                                },
                                                "name": {
                                                    "type": [
                                                        "string",
                                                        "null"
                                                    ]
                                                },
                                                "subscribed": {
                                                    "type": "boolean",
                                                    "default": true
                                                }
                                            },
                                            "required": [
                                                "number"
                                            ]
                                        }
                                    },
                                    "onDuplicate": {
                                        "type": "string",
                                        "description": "What to do with a number already in the list. `update` is the default because it is what a sync means; `skip` leaves the stored row alone; `error` marks the row `failed` with `contact_exists` and still processes the rest.",
                                        "enum": [
                                            "update",
                                            "skip",
                                            "error"
                                        ],
                                        "default": "update"
                                    },
                                    "defaultCountry": {
                                        "type": "string",
                                        "description": "As on `POST /contacts`, and applied to every row in the batch. **This is the one that matters for a nightly sync**: the duplicate check is an exact string match, so without it a list stored in E.164 and a store sending local-form numbers never agree, and every run creates every customer again. An invalid code refuses the whole request rather than marking every row `failed`.",
                                        "example": "GB"
                                    }
                                },
                                "required": [
                                    "listId",
                                    "contacts"
                                ]
                            },
                            "example": {
                                "listId": 123,
                                "contacts": [
                                    "…"
                                ],
                                "defaultCountry": "GB"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ContactBulkResult"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/contacts/bulk\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"listId\":123,\"contacts\":[\"…\"],\"defaultCountry\":\"GB\"}'"
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/contacts/bulk\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Idempotency-Key: \" . bin2hex(random_bytes(16)),\n        \"Content-Type: application/json\",\n    ],\n    CURLOPT_POSTFIELDS     => json_encode([\n        \"listId\" => 123,\n        \"contacts\" => [\"…\"],\n        \"defaultCountry\" => \"GB\",\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/contacts/bulk\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    listId: 123,\n    contacts: [\"…\"],\n    defaultCountry: \"GB\",\n  }),\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import uuid\nimport requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/contacts/bulk\",\n    headers={\n        \"Authorization\": \"Bearer sboxk_live_your_key_here\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json={\n        \"listId\": 123,\n        \"contacts\": [\"…\"],\n        \"defaultCountry\": \"GB\",\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/contacts/{id}": {
            "patch": {
                "operationId": "updateContact",
                "summary": "Rename, subscribe or unsubscribe a contact",
                "tags": [
                    "Contacts"
                ],
                "description": "`subscribed: false` is how an opt-out is recorded. The contact stays, every send skips them, and `contact.unsubscribed` fires — deleting them instead would let the next import silently put them back. Re-sending the same value is a no-op and does not fire the event twice.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The contact's id."
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "description": "An empty string clears it."
                                    },
                                    "subscribed": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": []
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Contact"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X PATCH \"https://app.sms-gateway.app/api/v1/contacts/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/contacts/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"PATCH\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/contacts/123\", {\n  method: \"PATCH\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.patch(\n    \"https://app.sms-gateway.app/api/v1/contacts/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "delete": {
                "operationId": "deleteContact",
                "summary": "Delete a contact",
                "tags": [
                    "Contacts"
                ],
                "description": "To honour an opt-out, PATCH `subscribed: false` instead — a deleted contact is re-created by the next import that names them.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The contact's id."
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X DELETE \"https://app.sms-gateway.app/api/v1/contacts/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/contacts/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"DELETE\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/contacts/123\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.delete(\n    \"https://app.sms-gateway.app/api/v1/contacts/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/blacklist": {
            "get": {
                "operationId": "listBlacklist",
                "summary": "List suppressed numbers",
                "tags": [
                    "Blacklist"
                ],
                "description": "The account-wide never-send list. Every send has always been filtered against it — it is why `accepted` can be lower than the number of recipients — and until now only the panel could write to it.\n\n**Suppression is an exact string match.** `+447700900123` and `447700900123` are two different entries, and suppressing one does not suppress the other. Store the number in the same form you send it in.",
                "parameters": [
                    {
                        "name": "number",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Exact match — the same comparison the send path makes, so this is how you ask whether a number is actually suppressed."
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "minimum": 1,
                            "maximum": 200
                        },
                        "description": "Page size."
                    },
                    {
                        "name": "startingAfter",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The `nextCursor` from the previous page."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/BlacklistPage"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/blacklist\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/blacklist\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/blacklist\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/blacklist\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "post": {
                "operationId": "addToBlacklist",
                "summary": "Suppress a number",
                "tags": [
                    "Blacklist"
                ],
                "description": "The endpoint STOP handling needs. Writing `subscribed: false` on a contact only silences that one list; the same person in a second list still gets messaged. This is account-wide.\n\n**A number that is already suppressed answers 200 with the entry that was already there, not a 409** — the intent is a state, not an event, and a receiver processing a STOP keyword has no idea whether it has seen that keyword before. A number that was not suppressed answers 201. Neither is a failure, so no `Idempotency-Key` is needed.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "number": {
                                        "type": "string",
                                        "description": "**Stored and returned verbatim** — we never rewrite it. At most 16 characters — E.164's fifteen digits and a `+`. Suppress the number in the same form you send it in; where you cannot, send `defaultCountry` below.",
                                        "maxLength": 16,
                                        "example": "+11234567890"
                                    },
                                    "defaultCountry": {
                                        "type": "string",
                                        "description": "Two-letter ISO-3166 code, and it does **not** change what is stored. It is how a number written in local form — `07700900123` — is recognised as the same person as `+447700900123` when a message goes out: the canonical form is derived once, kept beside the row, and matched in addition to the literal. Matching can only ever suppress more, never less. Omitted, only the literal is matched, which is what has always happened. A number nothing can vouch for — a short code — gets no canonical form and keeps its literal either way.",
                                        "example": "GB"
                                    }
                                },
                                "required": [
                                    "number"
                                ]
                            },
                            "example": {
                                "number": "+11234567890",
                                "defaultCountry": "GB"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/BlacklistEntry"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "201": {
                        "description": "Created.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/BlacklistEntry"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/blacklist\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"number\":\"+11234567890\",\"defaultCountry\":\"GB\"}'"
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/blacklist\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Content-Type: application/json\",\n    ],\n    CURLOPT_POSTFIELDS     => json_encode([\n        \"number\" => \"+11234567890\",\n        \"defaultCountry\" => \"GB\",\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/blacklist\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    number: \"+11234567890\",\n    defaultCountry: \"GB\",\n  }),\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/blacklist\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n    json={\n        \"number\": \"+11234567890\",\n        \"defaultCountry\": \"GB\",\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "delete": {
                "operationId": "removeFromBlacklistByNumber",
                "summary": "Un-suppress a number",
                "tags": [
                    "Blacklist"
                ],
                "description": "Addressed by value rather than by id, because a receiver processing a START keyword has the number the handset reported and nothing else.\n\n**204 whether a row went or not.** A number that was never suppressed is already in the state the caller is asking for, and a 404 there would make every START handler treat its own success as an error.",
                "parameters": [
                    {
                        "name": "number",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "example": "+11234567890"
                        }
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X DELETE \"https://app.sms-gateway.app/api/v1/blacklist?number=%2B11234567890\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/blacklist?number=%2B11234567890\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"DELETE\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/blacklist?number=%2B11234567890\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.delete(\n    \"https://app.sms-gateway.app/api/v1/blacklist?number=%2B11234567890\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/blacklist/{id}": {
            "delete": {
                "operationId": "removeFromBlacklist",
                "summary": "Un-suppress a number by id",
                "tags": [
                    "Blacklist"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The blacklist's id."
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X DELETE \"https://app.sms-gateway.app/api/v1/blacklist/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/blacklist/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"DELETE\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/blacklist/123\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.delete(\n    \"https://app.sms-gateway.app/api/v1/blacklist/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/templates": {
            "get": {
                "operationId": "listTemplates",
                "summary": "List message templates",
                "tags": [
                    "Templates"
                ],
                "description": "The message bodies the customer has already written in the panel. Without this, every integration asks them to write the same text a second time in its own settings screen, and the two drift.\n\n**Nothing here substitutes anything.** Placeholders such as `%name%` are filled in by the send path against a contact, so read the template, substitute what you know, and post the result as `text`.",
                "parameters": [
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "minimum": 1,
                            "maximum": 200
                        },
                        "description": "Page size."
                    },
                    {
                        "name": "startingAfter",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The `nextCursor` from the previous page."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/TemplatePage"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/templates\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/templates\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/templates\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/templates\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "post": {
                "operationId": "createTemplate",
                "summary": "Create a message template",
                "tags": [
                    "Templates"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "example": "Shipping notice"
                                    },
                                    "text": {
                                        "type": "string",
                                        "description": "Stored and returned verbatim, entities and all.",
                                        "example": "Hi %name%, your order has shipped."
                                    }
                                },
                                "required": [
                                    "name",
                                    "text"
                                ]
                            },
                            "example": {
                                "name": "Shipping notice",
                                "text": "Hi %name%, your order has shipped."
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Template"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/templates\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Shipping notice\",\"text\":\"Hi %name%, your order has shipped.\"}'"
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/templates\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Content-Type: application/json\",\n    ],\n    CURLOPT_POSTFIELDS     => json_encode([\n        \"name\" => \"Shipping notice\",\n        \"text\" => \"Hi %name%, your order has shipped.\",\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/templates\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    name: \"Shipping notice\",\n    text: \"Hi %name%, your order has shipped.\",\n  }),\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/templates\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n    json={\n        \"name\": \"Shipping notice\",\n        \"text\": \"Hi %name%, your order has shipped.\",\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/templates/{id}": {
            "get": {
                "operationId": "getTemplate",
                "summary": "Retrieve a message template",
                "tags": [
                    "Templates"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The template's id."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Template"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/templates/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/templates/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/templates/123\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/templates/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "patch": {
                "operationId": "updateTemplate",
                "summary": "Rename or rewrite a message template",
                "tags": [
                    "Templates"
                ],
                "description": "An absent field is left alone. Neither can be cleared — a template with no body is a row that can only produce an invalid send later.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The template's id."
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "text": {
                                        "type": "string"
                                    }
                                }
                            },
                            "example": []
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Template"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X PATCH \"https://app.sms-gateway.app/api/v1/templates/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/templates/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"PATCH\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/templates/123\", {\n  method: \"PATCH\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.patch(\n    \"https://app.sms-gateway.app/api/v1/templates/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "delete": {
                "operationId": "deleteTemplate",
                "summary": "Delete a message template",
                "tags": [
                    "Templates"
                ],
                "description": "Nothing points at a template — it is copied into a message at compose time — so deleting one cannot orphan anything already sent.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The template's id."
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X DELETE \"https://app.sms-gateway.app/api/v1/templates/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/templates/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"DELETE\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/templates/123\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.delete(\n    \"https://app.sms-gateway.app/api/v1/templates/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/ussd": {
            "get": {
                "operationId": "listUssdRequests",
                "summary": "List USSD requests",
                "tags": [
                    "USSD"
                ],
                "parameters": [
                    {
                        "name": "deviceId",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer"
                        }
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "Pending",
                                "Received"
                            ]
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "minimum": 1,
                            "maximum": 200
                        },
                        "description": "Page size."
                    },
                    {
                        "name": "startingAfter",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The `nextCursor` from the previous page."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/UssdPage"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/ussd\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/ussd\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/ussd\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/ussd\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "post": {
                "operationId": "sendUssdRequest",
                "summary": "Dial a USSD code",
                "tags": [
                    "USSD"
                ],
                "description": "Asynchronous: this answers `Pending` with no response text, because there is none yet. The handset dials and the network answers seconds later — poll `GET /ussd/{id}` or subscribe to `ussd.response`. The device must be owned by this account, not merely shared with it: a USSD code can move money on a prepaid line, and a device shared for sending SMS was not shared for that.",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "maxLength": 255,
                            "example": "3f1b0c8a-9d2e-4c77-9f5a-2b6d1e0f4a83"
                        },
                        "description": "Optional, and every integration should send one. A retry carrying the key of a request that already succeeded is answered with that request's response — `Idempotency-Replayed: true` — instead of doing the work again. Keys are scoped to the account, remembered for 24 hours, and belong to exactly one request: reusing one with a different body is `409 idempotency_key_reused`, and reusing one while the first request is still running is `409 request_in_progress`. A failed request releases its key, so a corrected retry may keep it. Any printable string up to 255 characters; a UUID is ideal."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "request": {
                                        "type": "string",
                                        "example": "*123#"
                                    },
                                    "deviceId": {
                                        "type": "integer"
                                    },
                                    "simSlot": {
                                        "type": "integer",
                                        "description": "Omitted, the device chooses."
                                    }
                                },
                                "required": [
                                    "request",
                                    "deviceId"
                                ]
                            },
                            "example": {
                                "request": "*123#",
                                "deviceId": 123
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Accepted. Queued, not yet sent — a device collects it on its next poll.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Ussd"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Idempotency-Replayed": {
                                "$ref": "#/components/headers/Idempotency-Replayed"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/ussd\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"request\":\"*123#\",\"deviceId\":123}'"
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/ussd\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Idempotency-Key: \" . bin2hex(random_bytes(16)),\n        \"Content-Type: application/json\",\n    ],\n    CURLOPT_POSTFIELDS     => json_encode([\n        \"request\" => \"*123#\",\n        \"deviceId\" => 123,\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/ussd\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Idempotency-Key\": crypto.randomUUID(),\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    request: \"*123#\",\n    deviceId: 123,\n  }),\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import uuid\nimport requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/ussd\",\n    headers={\n        \"Authorization\": \"Bearer sboxk_live_your_key_here\",\n        \"Idempotency-Key\": str(uuid.uuid4()),\n    },\n    json={\n        \"request\": \"*123#\",\n        \"deviceId\": 123,\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/ussd/{id}": {
            "get": {
                "operationId": "getUssdRequest",
                "summary": "Retrieve a USSD request",
                "tags": [
                    "USSD"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The ussd's id."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Ussd"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/ussd/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/ussd/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/ussd/123\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/ussd/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/account": {
            "get": {
                "operationId": "getAccount",
                "summary": "Retrieve the account",
                "tags": [
                    "Account"
                ],
                "description": "Credits, limits and how much of each is used — plus which API key made the request, which is the only way to confirm from outside that a deployment picked up the credential you think it did. Null means unlimited throughout.",
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Account"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/account\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/account\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/account\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/account\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/subscription": {
            "get": {
                "operationId": "getSubscription",
                "summary": "Retrieve the current subscription",
                "tags": [
                    "Account"
                ],
                "description": "What the account is on and how the term ends. **An account that has never bought anything is not an error**: it answers 200 with `status` `FREE` and a null `plan`, which is the tier every account starts on. Read `renews` rather than guessing from `paymentMethod` — a crypto term is prepaid and expires instead of renewing. Credits and the device and contact limits are on `GET /account` and are deliberately not repeated here.",
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Subscription"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/subscription\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/subscription\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/subscription\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/subscription\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/preferences": {
            "get": {
                "operationId": "getPreferences",
                "summary": "Retrieve the account's sending preferences",
                "tags": [
                    "Account"
                ],
                "description": "The account settings that change what a send DOES, so a client can explain its own behaviour instead of looking broken. Two of them cause support tickets against us: with `reportDelivery` off no message ever reaches `Delivered`, so a dashboard drawing that column will never fill it; and a send accepted inside `sleepTime` sits on the handset until the window closes, which from outside is indistinguishable from a message that was lost.\n\n**There is no `PATCH`, deliberately.** These are the account holder's settings and they are changed in the panel, on `preferences.php`. Read them, warn about them, link to the panel — do not write them on somebody's behalf.",
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Preferences"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/preferences\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/preferences\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/preferences\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/preferences\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/connect/exchange": {
            "post": {
                "operationId": "exchangeConnectCode",
                "summary": "Exchange a connection code for an API key",
                "tags": [
                    "Account"
                ],
                "description": "The second half of the store-connect handshake, and **the only endpoint here besides `openapi.json` that takes no `Authorization` header** — it is how a caller with no credential obtains one.\n\nThe flow: your plugin sends the merchant to `connect.php` on the panel with the site URL, a PKCE `code_challenge` and a return URL on that same site. They sign in or register, approve, and the browser comes back to your return URL with `?smsgw_code=…&state=…`. **Your server** then calls this, from the back end, with the code and the verifier behind that challenge.\n\n**The key is never in a URL**, which is the whole reason for two steps: it would be in the browser history, the `Referer` of the next page your site loads, and two access logs. It is returned here, once, and nothing can repeat it — we store only a hash.\n\n**One live key per site.** Running the flow again for the same site revokes the previous key and issues a replacement, so four reinstalls leave one credential rather than four. **The key does not expire**; revoking it, from either side, is the only way a connection ends.\n\nEvery refusal is `400 invalid_code` with the same sentence — an unknown code, an expired one, one already used, one for a different site, and a verifier that does not match are deliberately indistinguishable. Start the flow again.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "code": {
                                        "type": "string",
                                        "description": "The `smsgw_code` from the redirect. Single use, and good for five minutes."
                                    },
                                    "verifier": {
                                        "type": "string",
                                        "description": "The PKCE code verifier — 43 to 128 characters — whose `BASE64URL(SHA256(verifier))` you sent to `connect.php` as the challenge. It must never leave your server."
                                    },
                                    "site": {
                                        "type": "string",
                                        "description": "The same site URL you started the flow with. Compared with what the merchant approved, so a code cannot be redeemed for a different store.",
                                        "example": "https://shop.example.com"
                                    }
                                },
                                "required": [
                                    "code",
                                    "verifier",
                                    "site"
                                ]
                            },
                            "example": {
                                "code": "…",
                                "verifier": "…",
                                "site": "https://shop.example.com"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/ConnectExchange"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "security": [],
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/connect/exchange\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"code\":\"…\",\"verifier\":\"…\",\"site\":\"https://shop.example.com\"}'"
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/connect/exchange\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Content-Type: application/json\",\n    ],\n    CURLOPT_POSTFIELDS     => json_encode([\n        \"code\" => \"…\",\n        \"verifier\" => \"…\",\n        \"site\" => \"https://shop.example.com\",\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/connect/exchange\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    code: \"…\",\n    verifier: \"…\",\n    site: \"https://shop.example.com\",\n  }),\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/connect/exchange\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n    json={\n        \"code\": \"…\",\n        \"verifier\": \"…\",\n        \"site\": \"https://shop.example.com\",\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/webhooks": {
            "get": {
                "operationId": "listWebhooks",
                "summary": "List webhook endpoints",
                "tags": [
                    "Webhooks"
                ],
                "parameters": [
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "minimum": 1,
                            "maximum": 200
                        },
                        "description": "Page size."
                    },
                    {
                        "name": "startingAfter",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The `nextCursor` from the previous page."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/WebhookPage"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/webhooks\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/webhooks\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/webhooks\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/webhooks\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "post": {
                "operationId": "createWebhook",
                "summary": "Create a webhook endpoint",
                "tags": [
                    "Webhooks"
                ],
                "description": "**The response carries the signing secret, and it is the only time it is ever returned.** There is no read path for it; somebody who loses it rotates. The URL is checked against the outbound guard here as well as at delivery time — private addresses and loopback are refused.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string",
                                        "example": "Order service"
                                    },
                                    "url": {
                                        "type": "string",
                                        "format": "uri",
                                        "example": "https://example.com/hooks/sms-gateway"
                                    },
                                    "events": {
                                        "type": "array",
                                        "description": "Event types, or `[\"*\"]` for every event including ones added later. `GET /webhooks/events` lists them.",
                                        "example": [
                                            "message.delivered",
                                            "message.failed"
                                        ],
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "format": {
                                        "type": "string",
                                        "enum": [
                                            "json",
                                            "xml"
                                        ],
                                        "default": "json"
                                    },
                                    "active": {
                                        "type": "boolean",
                                        "default": true
                                    }
                                },
                                "required": [
                                    "name",
                                    "url",
                                    "events"
                                ]
                            },
                            "example": {
                                "name": "Order service",
                                "url": "https://example.com/hooks/sms-gateway",
                                "events": [
                                    "message.delivered",
                                    "message.failed"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Created.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Webhook"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/webhooks\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"name\":\"Order service\",\"url\":\"https://example.com/hooks/sms-gateway\",\"events\":[\"message.delivered\",\"message.failed\"]}'"
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/webhooks\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n        \"Content-Type: application/json\",\n    ],\n    CURLOPT_POSTFIELDS     => json_encode([\n        \"name\" => \"Order service\",\n        \"url\" => \"https://example.com/hooks/sms-gateway\",\n        \"events\" => [\"message.delivered\", \"message.failed\"],\n    ]),\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/webhooks\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n    \"Content-Type\": \"application/json\",\n  },\n  body: JSON.stringify({\n    name: \"Order service\",\n    url: \"https://example.com/hooks/sms-gateway\",\n    events: [\"message.delivered\", \"message.failed\"],\n  }),\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/webhooks\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n    json={\n        \"name\": \"Order service\",\n        \"url\": \"https://example.com/hooks/sms-gateway\",\n        \"events\": [\"message.delivered\", \"message.failed\"],\n    },\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/webhooks/events": {
            "get": {
                "operationId": "listWebhookEvents",
                "summary": "List event types",
                "tags": [
                    "Webhooks"
                ],
                "description": "The catalogue, grouped by resource, with the sample payload each event sends. Assert against it in your own tests and a renamed event fails there rather than in production.",
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/EventCatalogue"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/webhooks/events\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/webhooks/events\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/webhooks/events\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/webhooks/events\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/webhooks/{id}": {
            "get": {
                "operationId": "getWebhook",
                "summary": "Retrieve a webhook endpoint",
                "tags": [
                    "Webhooks"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The webhook's id."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Webhook"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/webhooks/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/webhooks/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/webhooks/123\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/webhooks/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "patch": {
                "operationId": "updateWebhook",
                "summary": "Update a webhook endpoint",
                "tags": [
                    "Webhooks"
                ],
                "description": "Only the fields you send change. Switching a self-disabled endpoint back on with `active: true` clears its failure streak — otherwise it would disable itself again on the very next failure.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The webhook's id."
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "name": {
                                        "type": "string"
                                    },
                                    "url": {
                                        "type": "string",
                                        "format": "uri"
                                    },
                                    "events": {
                                        "type": "array",
                                        "items": {
                                            "type": "string"
                                        }
                                    },
                                    "format": {
                                        "type": "string",
                                        "enum": [
                                            "json",
                                            "xml"
                                        ]
                                    },
                                    "active": {
                                        "type": "boolean"
                                    }
                                }
                            },
                            "example": []
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Webhook"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X PATCH \"https://app.sms-gateway.app/api/v1/webhooks/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/webhooks/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"PATCH\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/webhooks/123\", {\n  method: \"PATCH\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.patch(\n    \"https://app.sms-gateway.app/api/v1/webhooks/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            },
            "delete": {
                "operationId": "deleteWebhook",
                "summary": "Delete a webhook endpoint",
                "tags": [
                    "Webhooks"
                ],
                "description": "Its delivery history goes with it.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The webhook's id."
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Deleted. No body.",
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X DELETE \"https://app.sms-gateway.app/api/v1/webhooks/123\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/webhooks/123\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"DELETE\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/webhooks/123\", {\n  method: \"DELETE\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.delete(\n    \"https://app.sms-gateway.app/api/v1/webhooks/123\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/webhooks/{id}/rotate-secret": {
            "post": {
                "operationId": "rotateWebhookSecret",
                "summary": "Rotate the signing secret",
                "tags": [
                    "Webhooks"
                ],
                "description": "**Returns the new secret once.** The old one keeps signing for `graceSeconds` so a receiver can be redeployed without dropping events — verify against both while the window is open.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The webhook's id."
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "graceSeconds": {
                                        "type": "integer",
                                        "description": "How long the old secret stays valid. Zero cuts it off now.",
                                        "default": 86400,
                                        "minimum": 0,
                                        "maximum": 604800
                                    }
                                }
                            },
                            "example": []
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Webhook"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/webhooks/123/rotate-secret\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/webhooks/123/rotate-secret\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/webhooks/123/rotate-secret\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/webhooks/123/rotate-secret\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/webhooks/{id}/test": {
            "post": {
                "operationId": "testWebhook",
                "summary": "Send a test event",
                "tags": [
                    "Webhooks"
                ],
                "description": "Queues a `webhook.test` event to this endpoint alone, whether or not it subscribes to that type — nobody subscribes to a test. Everything downstream is identical to a real event: same queue, same signing, same retries, same outbound checks. A test that took a shortcut would prove the URL resolves; this one proves your receiver verifies our signature. The worker drains within about ten seconds.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The webhook's id."
                    }
                ],
                "responses": {
                    "202": {
                        "description": "Accepted. Queued, not yet sent — a device collects it on its next poll.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/WebhookDelivery"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/webhooks/123/test\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/webhooks/123/test\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/webhooks/123/test\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/webhooks/123/test\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/webhooks/{id}/deliveries": {
            "get": {
                "operationId": "listWebhookDeliveries",
                "summary": "List deliveries",
                "tags": [
                    "Webhooks"
                ],
                "description": "What was sent, what came back, and what is still due. Includes the exact bytes signed, so a signature mismatch can be reproduced.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The webhook's id."
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "Queued",
                                "Delivering",
                                "Delivered",
                                "Failed",
                                "Expired"
                            ]
                        }
                    },
                    {
                        "name": "event",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "example": "message.delivered"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "default": 50,
                            "minimum": 1,
                            "maximum": 200
                        },
                        "description": "Page size."
                    },
                    {
                        "name": "startingAfter",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The `nextCursor` from the previous page."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Success.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/WebhookDeliveryPage"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "400": {
                        "description": "The request is wrong. `param` names the field. Retrying it unchanged will fail again.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X GET \"https://app.sms-gateway.app/api/v1/webhooks/123/deliveries?event=message.delivered\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/webhooks/123/deliveries?event=message.delivered\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"GET\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/webhooks/123/deliveries?event=message.delivered\", {\n  method: \"GET\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.get(\n    \"https://app.sms-gateway.app/api/v1/webhooks/123/deliveries?event=message.delivered\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        },
        "/webhooks/{id}/deliveries/{deliveryId}/replay": {
            "post": {
                "operationId": "replayWebhookDelivery",
                "summary": "Replay a delivery",
                "tags": [
                    "Webhooks"
                ],
                "description": "Queues the original bytes again as a new delivery — the first attempt's record is evidence and is left alone. The replay carries the original `eventId`, so a receiver deduplicating on `X-SmsGateway-Event-Id` sees the same event arriving twice, which is what it is. Allowed from any status, including `Delivered`: \"we processed it but our database was down\" is the ordinary reason to ask.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The webhook's id."
                    },
                    {
                        "name": "deliveryId",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The delivery's id, from `GET /webhooks/{id}/deliveries`."
                    }
                ],
                "responses": {
                    "202": {
                        "description": "Accepted. Queued, not yet sent — a device collects it on its next poll.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/WebhookDelivery"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "401": {
                        "description": "No key, or one we do not recognise.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "404": {
                        "description": "No such thing, or it does not belong to this account. The two are deliberately indistinguishable.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "409": {
                        "description": "The request is fine but the current state forbids it — a campaign that has already stopped, a contact that already exists.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        },
                        "headers": {
                            "X-Request-Id": {
                                "$ref": "#/components/headers/X-Request-Id"
                            },
                            "X-SmsGateway-Api-Version": {
                                "$ref": "#/components/headers/X-SmsGateway-Api-Version"
                            },
                            "Retry-After": {
                                "$ref": "#/components/headers/Retry-After"
                            }
                        }
                    }
                },
                "x-codeSamples": [
                    {
                        "lang": "Shell",
                        "label": "curl",
                        "source": "curl -X POST \"https://app.sms-gateway.app/api/v1/webhooks/123/deliveries/456/replay\" \\\n  -H \"Authorization: Bearer sboxk_live_your_key_here\""
                    },
                    {
                        "lang": "PHP",
                        "label": "PHP",
                        "source": "<?php\n\n$ch = curl_init(\"https://app.sms-gateway.app/api/v1/webhooks/123/deliveries/456/replay\");\ncurl_setopt_array($ch, [\n    CURLOPT_CUSTOMREQUEST  => \"POST\",\n    CURLOPT_RETURNTRANSFER => true,\n    CURLOPT_HTTPHEADER     => [\n        \"Authorization: Bearer sboxk_live_your_key_here\",\n    ],\n]);\n\n$response = json_decode(curl_exec($ch), true);\n$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);\ncurl_close($ch);\n"
                    },
                    {
                        "lang": "JavaScript",
                        "label": "Node",
                        "source": "const response = await fetch(\"https://app.sms-gateway.app/api/v1/webhooks/123/deliveries/456/replay\", {\n  method: \"POST\",\n  headers: {\n    Authorization: \"Bearer sboxk_live_your_key_here\",\n  },\n});\n\nconst data = await response.json();\n"
                    },
                    {
                        "lang": "Python",
                        "label": "Python",
                        "source": "import requests\n\nresponse = requests.post(\n    \"https://app.sms-gateway.app/api/v1/webhooks/123/deliveries/456/replay\",\n    headers={\"Authorization\": \"Bearer sboxk_live_your_key_here\"},\n)\n\ndata = response.json()\n"
                    }
                ]
            }
        }
    },
    "webhooks": {
        "message.received": {
            "post": {
                "operationId": "onMessageReceived",
                "summary": "An inbound SMS arrived on one of your devices.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `message.received`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "message.received"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "message.received"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "message": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 41822
                                                    },
                                                    "number": {
                                                        "type": "string",
                                                        "example": "+11234567890"
                                                    },
                                                    "text": {
                                                        "type": "string",
                                                        "example": "This is a test message."
                                                    },
                                                    "deviceId": {
                                                        "type": "integer",
                                                        "example": 3
                                                    },
                                                    "simSlot": {
                                                        "type": "integer",
                                                        "example": 0
                                                    },
                                                    "sentSimSlot": {
                                                        "type": "integer",
                                                        "example": 0
                                                    },
                                                    "sentSim": {
                                                        "type": "object",
                                                        "properties": {
                                                            "id": {
                                                                "type": "integer",
                                                                "example": 14
                                                            },
                                                            "label": {
                                                                "type": "string",
                                                                "example": "GrameenPhone"
                                                            }
                                                        }
                                                    },
                                                    "status": {
                                                        "type": "string",
                                                        "example": "Received"
                                                    },
                                                    "receivedAt": {
                                                        "type": "string",
                                                        "example": "2026-08-12T14:03:10Z",
                                                        "format": "date-time"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "message.received",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "message": {
                                        "id": 41822,
                                        "number": "+11234567890",
                                        "text": "This is a test message.",
                                        "deviceId": 3,
                                        "simSlot": 0,
                                        "sentSimSlot": 0,
                                        "sentSim": {
                                            "id": 14,
                                            "label": "GrameenPhone"
                                        },
                                        "status": "Received",
                                        "receivedAt": "2026-08-12T14:03:10Z"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        },
        "message.sent": {
            "post": {
                "operationId": "onMessageSent",
                "summary": "A device handed a message to the mobile network.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `message.sent`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "message.sent"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "message.sent"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "message": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 41823
                                                    },
                                                    "number": {
                                                        "type": "string",
                                                        "example": "+11234567890"
                                                    },
                                                    "text": {
                                                        "type": "string",
                                                        "example": "Your code is 4821."
                                                    },
                                                    "deviceId": {
                                                        "type": "integer",
                                                        "example": 3
                                                    },
                                                    "simSlot": {
                                                        "type": "integer",
                                                        "example": 0
                                                    },
                                                    "sentSimSlot": {
                                                        "type": "integer",
                                                        "example": 0
                                                    },
                                                    "sentSim": {
                                                        "type": "object",
                                                        "properties": {
                                                            "id": {
                                                                "type": "integer",
                                                                "example": 14
                                                            },
                                                            "label": {
                                                                "type": "string",
                                                                "example": "GrameenPhone"
                                                            }
                                                        }
                                                    },
                                                    "campaignId": {
                                                        "type": "integer",
                                                        "example": 17
                                                    },
                                                    "metadata": {
                                                        "type": "object",
                                                        "properties": {
                                                            "orderId": {
                                                                "type": "string",
                                                                "example": "1234"
                                                            }
                                                        }
                                                    },
                                                    "status": {
                                                        "type": "string",
                                                        "example": "Sent"
                                                    },
                                                    "sentAt": {
                                                        "type": "string",
                                                        "example": "2026-08-12T14:04:02Z",
                                                        "format": "date-time"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "message.sent",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "message": {
                                        "id": 41823,
                                        "number": "+11234567890",
                                        "text": "Your code is 4821.",
                                        "deviceId": 3,
                                        "simSlot": 0,
                                        "sentSimSlot": 0,
                                        "sentSim": {
                                            "id": 14,
                                            "label": "GrameenPhone"
                                        },
                                        "campaignId": 17,
                                        "metadata": {
                                            "orderId": "1234"
                                        },
                                        "status": "Sent",
                                        "sentAt": "2026-08-12T14:04:02Z"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        },
        "message.delivered": {
            "post": {
                "operationId": "onMessageDelivered",
                "summary": "The network confirmed delivery. Requires delivery reports to be enabled.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `message.delivered`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "message.delivered"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "message.delivered"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "message": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 41823
                                                    },
                                                    "number": {
                                                        "type": "string",
                                                        "example": "+11234567890"
                                                    },
                                                    "text": {
                                                        "type": "string",
                                                        "example": "Your code is 4821."
                                                    },
                                                    "deviceId": {
                                                        "type": "integer",
                                                        "example": 3
                                                    },
                                                    "simSlot": {
                                                        "type": "integer",
                                                        "example": 0
                                                    },
                                                    "sentSimSlot": {
                                                        "type": "integer",
                                                        "example": 0
                                                    },
                                                    "sentSim": {
                                                        "type": "object",
                                                        "properties": {
                                                            "id": {
                                                                "type": "integer",
                                                                "example": 14
                                                            },
                                                            "label": {
                                                                "type": "string",
                                                                "example": "GrameenPhone"
                                                            }
                                                        }
                                                    },
                                                    "campaignId": {
                                                        "type": "integer",
                                                        "example": 17
                                                    },
                                                    "metadata": {
                                                        "type": "object",
                                                        "properties": {
                                                            "orderId": {
                                                                "type": "string",
                                                                "example": "1234"
                                                            }
                                                        }
                                                    },
                                                    "status": {
                                                        "type": "string",
                                                        "example": "Delivered"
                                                    },
                                                    "sentAt": {
                                                        "type": "string",
                                                        "example": "2026-08-12T14:04:02Z",
                                                        "format": "date-time"
                                                    },
                                                    "deliveredAt": {
                                                        "type": "string",
                                                        "example": "2026-08-12T14:04:09Z",
                                                        "format": "date-time"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "message.delivered",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "message": {
                                        "id": 41823,
                                        "number": "+11234567890",
                                        "text": "Your code is 4821.",
                                        "deviceId": 3,
                                        "simSlot": 0,
                                        "sentSimSlot": 0,
                                        "sentSim": {
                                            "id": 14,
                                            "label": "GrameenPhone"
                                        },
                                        "campaignId": 17,
                                        "metadata": {
                                            "orderId": "1234"
                                        },
                                        "status": "Delivered",
                                        "sentAt": "2026-08-12T14:04:02Z",
                                        "deliveredAt": "2026-08-12T14:04:09Z"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        },
        "message.failed": {
            "post": {
                "operationId": "onMessageFailed",
                "summary": "A message could not be sent or was rejected by the network.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `message.failed`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "message.failed"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "message.failed"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "message": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 41824
                                                    },
                                                    "number": {
                                                        "type": "string",
                                                        "example": "+11234567890"
                                                    },
                                                    "text": {
                                                        "type": "string",
                                                        "example": "Your code is 4821."
                                                    },
                                                    "deviceId": {
                                                        "type": "integer",
                                                        "example": 3
                                                    },
                                                    "simSlot": {
                                                        "type": "integer",
                                                        "example": 0
                                                    },
                                                    "sentSimSlot": {
                                                        "type": "integer",
                                                        "example": 0
                                                    },
                                                    "sentSim": {
                                                        "type": "object",
                                                        "properties": {
                                                            "id": {
                                                                "type": "integer",
                                                                "example": 14
                                                            },
                                                            "label": {
                                                                "type": "string",
                                                                "example": "GrameenPhone"
                                                            }
                                                        }
                                                    },
                                                    "campaignId": {
                                                        "type": "integer",
                                                        "example": 17
                                                    },
                                                    "metadata": {
                                                        "type": "object",
                                                        "properties": {
                                                            "orderId": {
                                                                "type": "string",
                                                                "example": "1234"
                                                            }
                                                        }
                                                    },
                                                    "status": {
                                                        "type": "string",
                                                        "example": "Failed"
                                                    },
                                                    "resultCode": {
                                                        "type": "integer",
                                                        "example": 1
                                                    },
                                                    "errorCode": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "sentAt": {
                                                        "type": "string",
                                                        "example": "2026-08-12T14:04:02Z",
                                                        "format": "date-time"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "message.failed",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "message": {
                                        "id": 41824,
                                        "number": "+11234567890",
                                        "text": "Your code is 4821.",
                                        "deviceId": 3,
                                        "simSlot": 0,
                                        "sentSimSlot": 0,
                                        "sentSim": {
                                            "id": 14,
                                            "label": "GrameenPhone"
                                        },
                                        "campaignId": 17,
                                        "metadata": {
                                            "orderId": "1234"
                                        },
                                        "status": "Failed",
                                        "resultCode": 1,
                                        "errorCode": null,
                                        "sentAt": "2026-08-12T14:04:02Z"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        },
        "campaign.started": {
            "post": {
                "operationId": "onCampaignStarted",
                "summary": "A campaign began sending — either immediately or when its schedule came due.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `campaign.started`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "campaign.started"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "campaign.started"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "campaign": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 17
                                                    },
                                                    "name": {
                                                        "type": "string",
                                                        "example": "October promo"
                                                    },
                                                    "state": {
                                                        "type": "string",
                                                        "example": "Running"
                                                    },
                                                    "type": {
                                                        "type": "string",
                                                        "example": "sms"
                                                    },
                                                    "source": {
                                                        "type": "string",
                                                        "example": "api"
                                                    },
                                                    "metadata": {
                                                        "type": "object",
                                                        "properties": {
                                                            "orderId": {
                                                                "type": "string",
                                                                "example": "1234"
                                                            }
                                                        }
                                                    },
                                                    "total": {
                                                        "type": "integer",
                                                        "example": 1200
                                                    },
                                                    "startedAt": {
                                                        "type": "string",
                                                        "example": "2026-08-12T14:04:00Z",
                                                        "format": "date-time"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "campaign.started",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "campaign": {
                                        "id": 17,
                                        "name": "October promo",
                                        "state": "Running",
                                        "type": "sms",
                                        "source": "api",
                                        "metadata": {
                                            "orderId": "1234"
                                        },
                                        "total": 1200,
                                        "startedAt": "2026-08-12T14:04:00Z"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        },
        "campaign.completed": {
            "post": {
                "operationId": "onCampaignCompleted",
                "summary": "Every message in a campaign reached a final state.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `campaign.completed`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "campaign.completed"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "campaign.completed"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "campaign": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 17
                                                    },
                                                    "name": {
                                                        "type": "string",
                                                        "example": "October promo"
                                                    },
                                                    "state": {
                                                        "type": "string",
                                                        "example": "Completed"
                                                    },
                                                    "type": {
                                                        "type": "string",
                                                        "example": "sms"
                                                    },
                                                    "source": {
                                                        "type": "string",
                                                        "example": "api"
                                                    },
                                                    "metadata": {
                                                        "type": "object",
                                                        "properties": {
                                                            "orderId": {
                                                                "type": "string",
                                                                "example": "1234"
                                                            }
                                                        }
                                                    },
                                                    "total": {
                                                        "type": "integer",
                                                        "example": 1200
                                                    },
                                                    "sent": {
                                                        "type": "integer",
                                                        "example": 41
                                                    },
                                                    "delivered": {
                                                        "type": "integer",
                                                        "example": 1150
                                                    },
                                                    "failed": {
                                                        "type": "integer",
                                                        "example": 9
                                                    },
                                                    "startedAt": {
                                                        "type": "string",
                                                        "example": "2026-08-12T14:04:00Z",
                                                        "format": "date-time"
                                                    },
                                                    "completedAt": {
                                                        "type": "string",
                                                        "example": "2026-08-12T15:22:41Z",
                                                        "format": "date-time"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "campaign.completed",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "campaign": {
                                        "id": 17,
                                        "name": "October promo",
                                        "state": "Completed",
                                        "type": "sms",
                                        "source": "api",
                                        "metadata": {
                                            "orderId": "1234"
                                        },
                                        "total": 1200,
                                        "sent": 41,
                                        "delivered": 1150,
                                        "failed": 9,
                                        "startedAt": "2026-08-12T14:04:00Z",
                                        "completedAt": "2026-08-12T15:22:41Z"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        },
        "campaign.stopped": {
            "post": {
                "operationId": "onCampaignStopped",
                "summary": "A campaign was stopped before it finished; its remaining messages were cancelled.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `campaign.stopped`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "campaign.stopped"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "campaign.stopped"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "campaign": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 17
                                                    },
                                                    "name": {
                                                        "type": "string",
                                                        "example": "October promo"
                                                    },
                                                    "state": {
                                                        "type": "string",
                                                        "example": "Stopped"
                                                    },
                                                    "type": {
                                                        "type": "string",
                                                        "example": "sms"
                                                    },
                                                    "source": {
                                                        "type": "string",
                                                        "example": "api"
                                                    },
                                                    "metadata": {
                                                        "type": "object",
                                                        "properties": {
                                                            "orderId": {
                                                                "type": "string",
                                                                "example": "1234"
                                                            }
                                                        }
                                                    },
                                                    "total": {
                                                        "type": "integer",
                                                        "example": 1200
                                                    },
                                                    "canceled": {
                                                        "type": "integer",
                                                        "example": 800
                                                    },
                                                    "startedAt": {
                                                        "type": "string",
                                                        "example": "2026-08-12T14:04:00Z",
                                                        "format": "date-time"
                                                    },
                                                    "completedAt": {
                                                        "type": "string",
                                                        "example": "2026-08-12T14:31:07Z",
                                                        "format": "date-time"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "campaign.stopped",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "campaign": {
                                        "id": 17,
                                        "name": "October promo",
                                        "state": "Stopped",
                                        "type": "sms",
                                        "source": "api",
                                        "metadata": {
                                            "orderId": "1234"
                                        },
                                        "total": 1200,
                                        "canceled": 800,
                                        "startedAt": "2026-08-12T14:04:00Z",
                                        "completedAt": "2026-08-12T14:31:07Z"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        },
        "device.registered": {
            "post": {
                "operationId": "onDeviceRegistered",
                "summary": "A phone was paired to your account.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `device.registered`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "device.registered"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "device.registered"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "device": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 3
                                                    },
                                                    "name": {
                                                        "type": "string",
                                                        "example": "Warehouse phone"
                                                    },
                                                    "model": {
                                                        "type": "string",
                                                        "example": "Pixel 8"
                                                    },
                                                    "androidVersion": {
                                                        "type": "string",
                                                        "example": "15"
                                                    },
                                                    "appVersion": {
                                                        "type": "string",
                                                        "example": "3.1.0"
                                                    },
                                                    "enabled": {
                                                        "type": "boolean",
                                                        "example": true
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "device.registered",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "device": {
                                        "id": 3,
                                        "name": "Warehouse phone",
                                        "model": "Pixel 8",
                                        "androidVersion": "15",
                                        "appVersion": "3.1.0",
                                        "enabled": true
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        },
        "device.online": {
            "post": {
                "operationId": "onDeviceOnline",
                "summary": "A device that had stopped reporting started reporting again.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `device.online`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "device.online"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "device.online"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "device": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 3
                                                    },
                                                    "name": {
                                                        "type": "string",
                                                        "example": "Warehouse phone"
                                                    },
                                                    "model": {
                                                        "type": "string",
                                                        "example": "Pixel 8"
                                                    },
                                                    "enabled": {
                                                        "type": "boolean",
                                                        "example": true
                                                    },
                                                    "lastSeenAt": {
                                                        "type": "string",
                                                        "example": "2026-08-12T14:03:58Z",
                                                        "format": "date-time"
                                                    },
                                                    "offlineFor": {
                                                        "type": "integer",
                                                        "example": 412
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "device.online",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "device": {
                                        "id": 3,
                                        "name": "Warehouse phone",
                                        "model": "Pixel 8",
                                        "enabled": true,
                                        "lastSeenAt": "2026-08-12T14:03:58Z",
                                        "offlineFor": 412
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        },
        "device.offline": {
            "post": {
                "operationId": "onDeviceOffline",
                "summary": "A device stopped reporting in. Detected by a sweep, so it lags the real event by up to one sweep period.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `device.offline`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "device.offline"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "device.offline"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "device": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 3
                                                    },
                                                    "name": {
                                                        "type": "string",
                                                        "example": "Warehouse phone"
                                                    },
                                                    "model": {
                                                        "type": "string",
                                                        "example": "Pixel 8"
                                                    },
                                                    "enabled": {
                                                        "type": "boolean",
                                                        "example": true
                                                    },
                                                    "lastSeenAt": {
                                                        "type": "string",
                                                        "example": "2026-08-12T14:03:58Z",
                                                        "format": "date-time"
                                                    },
                                                    "offlineFor": {
                                                        "type": "integer",
                                                        "example": 70
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "device.offline",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "device": {
                                        "id": 3,
                                        "name": "Warehouse phone",
                                        "model": "Pixel 8",
                                        "enabled": true,
                                        "lastSeenAt": "2026-08-12T14:03:58Z",
                                        "offlineFor": 70
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        },
        "device.sim_changed": {
            "post": {
                "operationId": "onDeviceSim_changed",
                "summary": "The SIM card in one of your devices was replaced, or a tray was emptied.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `device.sim_changed`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "device.sim_changed"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "device.sim_changed"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "device": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 3
                                                    },
                                                    "name": {
                                                        "type": "string",
                                                        "example": "Warehouse phone"
                                                    },
                                                    "model": {
                                                        "type": "string",
                                                        "example": "Pixel 8"
                                                    },
                                                    "enabled": {
                                                        "type": "boolean",
                                                        "example": true
                                                    }
                                                }
                                            },
                                            "sim": {
                                                "type": "object",
                                                "properties": {
                                                    "slot": {
                                                        "type": "integer",
                                                        "example": 1
                                                    },
                                                    "previous": {
                                                        "type": "object",
                                                        "properties": {
                                                            "id": {
                                                                "type": "integer",
                                                                "example": 14
                                                            },
                                                            "label": {
                                                                "type": "string",
                                                                "example": "GrameenPhone"
                                                            }
                                                        }
                                                    },
                                                    "current": {
                                                        "type": "object",
                                                        "properties": {
                                                            "id": {
                                                                "type": "integer",
                                                                "example": 21
                                                            },
                                                            "label": {
                                                                "type": "string",
                                                                "example": "Robi"
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "device.sim_changed",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "device": {
                                        "id": 3,
                                        "name": "Warehouse phone",
                                        "model": "Pixel 8",
                                        "enabled": true
                                    },
                                    "sim": {
                                        "slot": 1,
                                        "previous": {
                                            "id": 14,
                                            "label": "GrameenPhone"
                                        },
                                        "current": {
                                            "id": 21,
                                            "label": "Robi"
                                        }
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        },
        "ussd.response": {
            "post": {
                "operationId": "onUssdResponse",
                "summary": "A device returned the result of a USSD request.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `ussd.response`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "ussd.response"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "ussd.response"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "ussd": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 91
                                                    },
                                                    "request": {
                                                        "type": "string",
                                                        "example": "*123#"
                                                    },
                                                    "response": {
                                                        "type": "string",
                                                        "example": "Your balance is $4.10"
                                                    },
                                                    "deviceId": {
                                                        "type": "integer",
                                                        "example": 3
                                                    },
                                                    "simSlot": {
                                                        "type": "integer",
                                                        "example": 0
                                                    },
                                                    "sentSimSlot": {
                                                        "type": "integer",
                                                        "example": 0
                                                    },
                                                    "sentSim": {
                                                        "type": "object",
                                                        "properties": {
                                                            "id": {
                                                                "type": "integer",
                                                                "example": 14
                                                            },
                                                            "label": {
                                                                "type": "string",
                                                                "example": "GrameenPhone"
                                                            }
                                                        }
                                                    },
                                                    "status": {
                                                        "type": "string",
                                                        "example": "Received"
                                                    },
                                                    "respondedAt": {
                                                        "type": "string",
                                                        "example": "2026-08-12T14:06:22Z",
                                                        "format": "date-time"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "ussd.response",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "ussd": {
                                        "id": 91,
                                        "request": "*123#",
                                        "response": "Your balance is $4.10",
                                        "deviceId": 3,
                                        "simSlot": 0,
                                        "sentSimSlot": 0,
                                        "sentSim": {
                                            "id": 14,
                                            "label": "GrameenPhone"
                                        },
                                        "status": "Received",
                                        "respondedAt": "2026-08-12T14:06:22Z"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        },
        "contact.subscribed": {
            "post": {
                "operationId": "onContactSubscribed",
                "summary": "A contact opted in, or was added to a list as subscribed.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `contact.subscribed`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "contact.subscribed"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "contact.subscribed"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "contact": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 5512
                                                    },
                                                    "name": {
                                                        "type": "string",
                                                        "example": "Jane Doe"
                                                    },
                                                    "number": {
                                                        "type": "string",
                                                        "example": "+11234567890"
                                                    },
                                                    "listId": {
                                                        "type": "integer",
                                                        "example": 4
                                                    },
                                                    "listName": {
                                                        "type": "string",
                                                        "example": "Newsletter"
                                                    },
                                                    "subscribed": {
                                                        "type": "boolean",
                                                        "example": true
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "contact.subscribed",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "contact": {
                                        "id": 5512,
                                        "name": "Jane Doe",
                                        "number": "+11234567890",
                                        "listId": 4,
                                        "listName": "Newsletter",
                                        "subscribed": true
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        },
        "contact.unsubscribed": {
            "post": {
                "operationId": "onContactUnsubscribed",
                "summary": "A contact opted out, by replying STOP or through the unsubscribe page.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `contact.unsubscribed`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "contact.unsubscribed"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "contact.unsubscribed"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "contact": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 5512
                                                    },
                                                    "name": {
                                                        "type": "string",
                                                        "example": "Jane Doe"
                                                    },
                                                    "number": {
                                                        "type": "string",
                                                        "example": "+11234567890"
                                                    },
                                                    "listId": {
                                                        "type": "integer",
                                                        "example": 4
                                                    },
                                                    "listName": {
                                                        "type": "string",
                                                        "example": "Newsletter"
                                                    },
                                                    "subscribed": {
                                                        "type": "boolean",
                                                        "example": false
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "contact.unsubscribed",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "contact": {
                                        "id": 5512,
                                        "name": "Jane Doe",
                                        "number": "+11234567890",
                                        "listId": 4,
                                        "listName": "Newsletter",
                                        "subscribed": false
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        },
        "webhook.test": {
            "post": {
                "operationId": "onWebhookTest",
                "summary": "A test event, sent from the panel. Never emitted by real activity.",
                "tags": [
                    "Events"
                ],
                "description": "Sent to every endpoint of yours subscribed to `webhook.test`.\n\nVerify `X-SmsGateway-Signature` before trusting the body: it is `v1=` followed by the hex HMAC-SHA256 of `\"{timestamp}.{body}\"` keyed with your endpoint's secret, where `{timestamp}` is the `X-SmsGateway-Timestamp` header. The timestamp is inside the signature, not merely beside it, so a captured request cannot be replayed later. During a secret rotation the header carries both signatures, space-separated — accept either.\n\nAnswer any 2xx. Anything else is retried with backoff up to six times over about eight hours; twenty consecutive failures switch the endpoint off. Deduplicate on `X-SmsGateway-Event-Id` — a retry and a replay both carry the original event's id.",
                "security": [],
                "parameters": [
                    {
                        "name": "X-SmsGateway-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "`v1=<hex hmac-sha256>`, space-separated during a rotation."
                    },
                    {
                        "name": "X-SmsGateway-Timestamp",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Unix seconds. Part of the signed material — reject anything older than your tolerance, five minutes is usual."
                    },
                    {
                        "name": "X-SmsGateway-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The event type. Also in the body as `type`.",
                        "example": "webhook.test"
                    },
                    {
                        "name": "X-SmsGateway-Event-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Deduplicate on this. Stable across retries and replays."
                    },
                    {
                        "name": "X-SmsGateway-Delivery-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "This attempt's id. Changes on a replay."
                    },
                    {
                        "name": "X-SmsGateway-Webhook-Id",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Which of your endpoints this went to."
                    },
                    {
                        "name": "X-SmsGateway-Attempt",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "1 on the first try."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "id": {
                                        "type": "string",
                                        "description": "Unique per event.",
                                        "example": "evt_01K2F8QW3N4RXB7M"
                                    },
                                    "type": {
                                        "type": "string",
                                        "const": "webhook.test"
                                    },
                                    "createdAt": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "apiVersion": {
                                        "type": "string",
                                        "example": "2026-08-12"
                                    },
                                    "data": {
                                        "type": "object",
                                        "properties": {
                                            "webhook": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "integer",
                                                        "example": 12
                                                    },
                                                    "name": {
                                                        "type": "string",
                                                        "example": "My endpoint"
                                                    },
                                                    "message": {
                                                        "type": "string",
                                                        "example": "If you are reading this, your endpoint is receiving and verifying SMS Gateway webhooks correctly."
                                                    }
                                                }
                                            }
                                        }
                                    }
                                },
                                "required": [
                                    "id",
                                    "type",
                                    "createdAt",
                                    "apiVersion",
                                    "data"
                                ]
                            },
                            "example": {
                                "id": "evt_01K2F8QW3N4RXB7M",
                                "type": "webhook.test",
                                "createdAt": "2026-08-12T14:03:10Z",
                                "apiVersion": "2026-08-12",
                                "data": {
                                    "webhook": {
                                        "id": 12,
                                        "name": "My endpoint",
                                        "message": "If you are reading this, your endpoint is receiving and verifying SMS Gateway webhooks correctly."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Acknowledged. Any 2xx will do; the body is ignored beyond the first few kilobytes, which we keep for the delivery log."
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "description": "Your API key, as `Authorization: Bearer sboxk_live_…`. Keys are created in the panel and shown once. There is no other authentication — no `?key=`, no email and password."
            }
        },
        "headers": {
            "X-Request-Id": {
                "description": "This request's id. Log it. It is the only thing that finds one request in our logs, and it is what a 500 asks you to quote.",
                "schema": {
                    "type": "string"
                }
            },
            "X-SmsGateway-Api-Version": {
                "description": "The API's contract version. **Do not pin on it and do not refuse a response whose value you do not know.** It is not bumped when a new optional field, response key or endpoint is added — only a change that could break a caller moves it, and there has not been one.",
                "schema": {
                    "type": "string"
                }
            },
            "Idempotency-Replayed": {
                "description": "Present and `true` when this response is a stored copy of an earlier request carrying the same `Idempotency-Key` — **nothing was sent or created this time**. Absent on a fresh request. Treat a replay as success: it means the work you are retrying already happened.",
                "schema": {
                    "type": "string",
                    "enum": [
                        "true"
                    ]
                }
            },
            "Retry-After": {
                "description": "Seconds to wait before repeating this exact request. Sent with `409 request_in_progress` while an earlier request holding the same `Idempotency-Key` is still running, and with any `429`. The request is not wrong — sleep and send it again, key and all.",
                "schema": {
                    "type": "integer",
                    "minimum": 1
                }
            }
        },
        "schemas": {
            "Message": {
                "type": "object",
                "description": "One message, outbound or inbound. A `status` of `Received` is the inbound case and reads differently throughout: it has a `receivedAt` rather than a `sentAt`, no campaign, and cost nothing to receive on most installations.",
                "properties": {
                    "id": {
                        "type": "integer",
                        "example": 41822
                    },
                    "number": {
                        "type": "string",
                        "example": "+11234567890"
                    },
                    "text": {
                        "type": "string"
                    },
                    "type": {
                        "type": "string",
                        "enum": [
                            "sms",
                            "mms"
                        ],
                        "description": "`mms` when the send carried attachments. A long text is still `sms` — it is sent as several parts and billed as several on most installations, which is what `POST /messages/preview` answers."
                    },
                    "deviceId": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "simSlot": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The SIM slot the send asked for. Null when the send was not pinned to one, which is most sends."
                    },
                    "sentSimSlot": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The SIM slot the handset actually used. Differs from `simSlot` on an unpinned send and after SIM failover. Null for a message sent by an app build that does not report it."
                    },
                    "sentSim": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "description": "The card this message went out on, as it was at the moment of sending. `label` is a value copy and stays correct after the card has been removed from the phone; `id` is null when the card could not be identified.",
                        "properties": {
                            "id": {
                                "type": [
                                    "integer",
                                    "null"
                                ]
                            },
                            "label": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "example": "GrameenPhone"
                            }
                        }
                    },
                    "campaignId": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "metadata": {
                        "type": "object",
                        "description": "The `metadata` of the campaign this message belongs to, copied at the moment the event was raised. Present only when the send carried some. This is what lets a webhook receiver say which of its own records an event is about; an inbound message belongs to no campaign and never carries it."
                    },
                    "segments": {
                        "type": "integer",
                        "description": "How many parts the carrier sees. A GSM-7 body fits 160 characters in one part and 153 in each part of a longer one; any character outside that alphabet — an emoji, a curly quote, most non-Latin scripts — puts the whole message into UCS-2, where the budget is 70 and 67. Where the operator bills per part, this is what the message cost.",
                        "example": 1
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "Pending",
                            "Queued",
                            "Scheduled",
                            "Sent",
                            "Delivered",
                            "Failed",
                            "Canceled",
                            "Received"
                        ],
                        "description": "Where this message has got to. **Three of these are still in flight and must not be rendered as a failure**, which is the mistake worth naming first: a client that treats anything other than `Delivered` as trouble reports one within seconds of every send.\n\n- `Pending` — written, waiting for a handset to claim it.\n- `Queued` — claimed by a device, not yet handed to the radio.\n- `Scheduled` — held until `scheduleAt`. It has not been attempted.\n- `Sent` — the handset gave it to the network. **Terminal on its own**: a delivery report is not guaranteed, and on many networks and most short codes one never arrives, so a message can legitimately stay `Sent` for ever.\n- `Delivered` — the network confirmed it reached the handset. Terminal. A message that reaches this has left `Sent`, so the two counts are disjoint and never both true of one row.\n- `Failed` — terminal. The credit is not returned.\n- `Canceled` — withdrawn before it was attempted, by `DELETE /messages/{id}` or by stopping its campaign. Terminal, and it refunds nothing.\n- `Received` — inbound. This message came in; nothing sent it."
                    },
                    "sentAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the message was queued. Absent on a received message."
                    },
                    "deliveredAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the network confirmed delivery. Present only on a `Delivered` message, and therefore only when delivery reports are enabled on the account — no other status carries it."
                    },
                    "receivedAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "Inbound messages only — when it arrived."
                    },
                    "resultCode": {
                        "type": "integer",
                        "description": "Present only when the device reported one."
                    },
                    "errorCode": {
                        "type": "integer",
                        "description": "Present only when the device reported one."
                    },
                    "groupId": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "One device's shard of a campaign. A campaign sending from three phones has three of these, and it is the handle for asking what one phone did."
                    },
                    "retries": {
                        "type": "integer",
                        "description": "How many times sending has been attempted again after a failure. Zero on almost every row."
                    },
                    "scheduledAt": {
                        "type": "string",
                        "format": "date-time",
                        "description": "When this message is held until. Present only on a message that was scheduled — including one that has since sent, where it says when it was meant to go rather than when it went."
                    },
                    "contactName": {
                        "type": "string",
                        "description": "The name this account has against `number` in a contacts list. Absent when the number is not in one.",
                        "example": "Rahim Uddin"
                    },
                    "campaignName": {
                        "type": "string",
                        "description": "The name of the campaign in `campaignId`. Absent where there is no campaign — a reply, an unmanaged send, anything from before the campaign table existed."
                    },
                    "deviceName": {
                        "type": "string",
                        "description": "**The name YOU gave this handset**, not the name its owner gave it. A phone shared between several accounts has a different name in each, and this is always yours; where you never named it, its model. On a removed device this is the name it had when it was unpaired, and `deviceRemoved` is set alongside.",
                        "example": "Chattogram desk"
                    },
                    "deviceRemoved": {
                        "type": "boolean",
                        "description": "Present and `true` when the handset that sent this message is gone — unpaired, or a shared phone its owner has since withdrawn from your account. `deviceId` is null on an unpaired one, so this is what tells that case apart from a message no phone has picked up yet. Absent, never `false`."
                    },
                    "sentBy": {
                        "type": "object",
                        "description": "The team member who sent it. **Absent means the account holder sent it themselves** — that is the ordinary case, and it is also every message written before team members existed.",
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "name": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            }
                        }
                    },
                    "attachments": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "What an `mms` carried. **These are paths on this installation, not links** — an entry like `uploads/33/ab12x-invoice.pdf` is deliberately relative, because publishing it as a URL would make this response a permanent, unauthenticated link to a customer's file for anyone the response is forwarded to. An entry that is already a full URL is one you supplied to `POST /messages` yourself, returned as you sent it."
                    }
                },
                "required": [
                    "id",
                    "number",
                    "text",
                    "type",
                    "status"
                ]
            },
            "MessageCount": {
                "type": "object",
                "description": "A total over the same filters `GET /messages` takes.",
                "properties": {
                    "count": {
                        "type": "integer",
                        "description": "How many messages match. Never above the cap.",
                        "example": 1842
                    },
                    "exact": {
                        "type": "boolean",
                        "description": "**False means `count` is the cap, not the answer** — there are at least that many and the query stopped counting. True means the number is the number.",
                        "example": true
                    }
                },
                "required": [
                    "count",
                    "exact"
                ]
            },
            "MessageBatch": {
                "type": "object",
                "description": "The result of a send. Nothing has left the device yet.",
                "properties": {
                    "campaignId": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Every send is a campaign, so it can be paused or stopped later. **Null** for a send made with `campaign: false`, which has none and never will, and null on a `dryRun`, which minted nothing."
                    },
                    "accepted": {
                        "type": "integer",
                        "description": "How many messages were queued. Lower than the number of recipients when some were blacklisted."
                    },
                    "scheduledAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "messages": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/Message"
                        }
                    },
                    "warnings": {
                        "type": "array",
                        "description": "Numbers we do not believe in. **Every message in this response was still queued, still charged and still sent** — a warning is advice and never a refusal, so a send that worked yesterday works today whatever appears here. Absent entirely when there is nothing to say. Turn warnings into refusals for your own account with strict number checking, under Preferences in the panel.",
                        "items": {
                            "$ref": "#/components/schemas/NumberWarning"
                        }
                    },
                    "warningsTruncated": {
                        "type": "boolean",
                        "description": "Present and true when there were more than 100 warnings and the list was cut. The messages beyond the cut were still sent; only the explanations were dropped."
                    },
                    "dryRun": {
                        "type": "boolean",
                        "description": "Present and true when `dryRun` was set. Every `id` is null, nothing was minted, nothing was charged and nothing will be sent."
                    }
                },
                "required": [
                    "campaignId",
                    "accepted",
                    "messages"
                ]
            },
            "NumberWarning": {
                "type": "object",
                "description": "One number, and what is wrong with it.",
                "properties": {
                    "number": {
                        "type": "string",
                        "description": "Exactly as you sent it.",
                        "example": "1"
                    },
                    "code": {
                        "type": "string",
                        "enum": [
                            "implausible_length",
                            "unknown_country_code",
                            "unallocated_range",
                            "not_a_mobile_number"
                        ],
                        "description": "`implausible_length` — a digit count no numbering plan uses. `unknown_country_code` — a `+` prefix whose country calling code has not been assigned. `unallocated_range` — parses, but its own country does not list the range as allocated; this is what catches the `+1 555…` numbers every example and every test fixture uses. `not_a_mobile_number` — its country lists it as a fixed line.",
                        "example": "implausible_length"
                    },
                    "message": {
                        "type": "string",
                        "description": "A sentence for a human reading a log.",
                        "example": "Sent as given. 1 digit is not a length any numbering plan uses, so this is unlikely to reach anybody."
                    }
                },
                "required": [
                    "number",
                    "code",
                    "message"
                ]
            },
            "MessagePreview": {
                "type": "object",
                "description": "What a send would cost. Nothing is queued, charged or sent.",
                "properties": {
                    "recipients": {
                        "type": "integer",
                        "description": "How many messages would be queued, after suppression."
                    },
                    "suppressed": {
                        "type": "integer",
                        "description": "Recipients on your blacklist. You cannot compute this yourself."
                    },
                    "billableCredits": {
                        "type": "integer",
                        "description": "What this would cost, from the same function the send charges with — so on an install that bills per part, this is the per-part price."
                    },
                    "encoding": {
                        "type": "string",
                        "enum": [
                            "GSM-7",
                            "UCS-2"
                        ],
                        "description": "The alphabet this body forces."
                    },
                    "characters": {
                        "type": "integer",
                        "description": "Length of the body."
                    },
                    "charactersPerPart": {
                        "type": "integer",
                        "description": "The budget for the parts this body actually has — 160 while it still fits in one, 153 once it does not."
                    },
                    "partsPerMessage": {
                        "type": "integer"
                    },
                    "estimatedParts": {
                        "type": "integer",
                        "description": "`partsPerMessage` × `recipients`, which is what the carrier counts."
                    },
                    "warnings": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/NumberWarning"
                        }
                    }
                },
                "required": [
                    "recipients",
                    "suppressed",
                    "billableCredits",
                    "encoding",
                    "partsPerMessage"
                ]
            },
            "MessagePage": {
                "type": "object",
                "description": "One page of messages, newest first.\n\nPagination is by cursor and not by offset: pass this page's `nextCursor` back as `startingAfter` to get the next one, and stop when `hasMore` is false. Do not build a cursor yourself and do not assume it is an id — it is opaque and the only valid source for one is a previous page.",
                "properties": {
                    "data": {
                        "type": "array",
                        "description": "The messages on this page, ordered by id descending, so the most recent is first. An empty array is an ordinary answer and not an error — it is what a filter that matches nothing returns.",
                        "items": {
                            "$ref": "#/components/schemas/Message"
                        }
                    },
                    "hasMore": {
                        "type": "boolean",
                        "description": "Whether another page exists. It is an observation and not an estimate — one row past the page is read to answer it — so `false` means there is genuinely nothing after this, and a caller may stop without a final empty request."
                    },
                    "nextCursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Pass as `startingAfter` for the next page. Null exactly when `hasMore` is false, so either one may be used as the loop condition — they cannot disagree."
                    }
                },
                "required": [
                    "data",
                    "hasMore",
                    "nextCursor"
                ]
            },
            "Campaign": {
                "type": "object",
                "description": "A send, with its counts. The counts are one aggregate over the campaign's messages and are what a progress bar is drawn from.",
                "properties": {
                    "id": {
                        "type": "integer",
                        "example": 53
                    },
                    "name": {
                        "type": "string"
                    },
                    "state": {
                        "type": "string",
                        "enum": [
                            "Draft",
                            "Expanding",
                            "Scheduled",
                            "Running",
                            "Paused",
                            "Completed",
                            "Stopped"
                        ],
                        "description": "**`Expanding` is the state to handle before any other.** A campaign over a contact list does not have its messages yet when `POST /campaigns` answers — a worker mints them — so every count below is `0` and a progress bar drawn from them shows `0/0`. That is not an empty campaign; poll until the state moves.\n\n- `Draft` — created, nothing minted.\n- `Expanding` — a worker is minting the messages.\n- `Scheduled` — minted, waiting for `scheduleAt`.\n- `Running` — handsets are working through it.\n- `Paused` — `POST /campaigns/{id}/pause`. Resumable.\n- `Completed` — every message reached a terminal status. Terminal.\n- `Stopped` — `POST /campaigns/{id}/stop`. Everything not yet attempted was canceled. Terminal, and not resumable."
                    },
                    "type": {
                        "type": "string",
                        "enum": [
                            "sms",
                            "mms"
                        ],
                        "description": "`mms` when the send carried attachments."
                    },
                    "source": {
                        "type": "string",
                        "enum": [
                            "numbers",
                            "contacts",
                            "spreadsheet",
                            "api",
                            "resend"
                        ],
                        "description": "How the recipients were named. `api` is `POST /messages`; `contacts` is `POST /campaigns`."
                    },
                    "metadata": {
                        "type": "object",
                        "description": "Whatever you sent as `metadata` when the send was created, returned verbatim. Absent when there was none. Opaque to us: never read, indexed or filtered on.",
                        "example": {
                            "orderId": "1234"
                        }
                    },
                    "createdAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "startedAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "completedAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "total": {
                        "type": "integer",
                        "description": "Messages minted for this campaign. **`0` while the state is `Draft` or `Expanding`** — the rows do not exist yet."
                    },
                    "sent": {
                        "type": "integer",
                        "description": "Handed to the network, with no delivery report yet. **Disjoint from `delivered`** — a message that is confirmed leaves this count for that one, so the two are never added together."
                    },
                    "delivered": {
                        "type": "integer",
                        "description": "Confirmed by the network. On networks that send no delivery reports this stays `0` however well the campaign went, so it is the wrong number to drive a success rate from — `sent + delivered` is the one that means \"reached the network\"."
                    },
                    "failed": {
                        "type": "integer",
                        "description": "Terminal failures. Credits are not returned."
                    },
                    "pending": {
                        "type": "integer",
                        "description": "Still in flight — the `Pending`, `Queued` and `Scheduled` messages together. It is derived, not counted: `total` minus the other four, so **the five always sum to `total` exactly** and a progress bar built on them cannot overflow."
                    },
                    "canceled": {
                        "type": "integer",
                        "description": "Withdrawn before being attempted, individually or by stopping the campaign."
                    },
                    "accepted": {
                        "type": "integer",
                        "description": "On create only: how many recipients the named lists came to, de-duplicated by number. The campaign is returned in `Expanding` and the messages are written by a worker, so this is what was accepted rather than what exists yet — blacklisted numbers are dropped during that write and `total` will be the smaller figure. Poll `GET /campaigns/{id}`."
                    },
                    "estimatedCredits": {
                        "type": "integer",
                        "description": "On a dry run only: what expanding these lists would cost, from the same function the send charges with."
                    },
                    "dryRun": {
                        "type": "boolean",
                        "description": "Present and true when `dryRun` was set. `id` is null, no campaign row exists and no expansion was queued."
                    }
                },
                "required": [
                    "id",
                    "name",
                    "state",
                    "type",
                    "source"
                ]
            },
            "CampaignPage": {
                "type": "object",
                "description": "One page of campaigns, newest first.\n\nPagination is by cursor and not by offset: pass this page's `nextCursor` back as `startingAfter` to get the next one, and stop when `hasMore` is false. Do not build a cursor yourself and do not assume it is an id — it is opaque and the only valid source for one is a previous page.",
                "properties": {
                    "data": {
                        "type": "array",
                        "description": "The campaigns on this page, ordered by id descending, so the most recent is first. An empty array is an ordinary answer and not an error — it is what a filter that matches nothing returns.",
                        "items": {
                            "$ref": "#/components/schemas/Campaign"
                        }
                    },
                    "hasMore": {
                        "type": "boolean",
                        "description": "Whether another page exists. It is an observation and not an estimate — one row past the page is read to answer it — so `false` means there is genuinely nothing after this, and a caller may stop without a final empty request."
                    },
                    "nextCursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Pass as `startingAfter` for the next page. Null exactly when `hasMore` is false, so either one may be used as the loop condition — they cannot disagree."
                    }
                },
                "required": [
                    "data",
                    "hasMore",
                    "nextCursor"
                ]
            },
            "Device": {
                "type": "object",
                "description": "A paired handset. Devices are created by pairing the app, never through this API.",
                "properties": {
                    "id": {
                        "type": "integer",
                        "example": 3
                    },
                    "name": {
                        "type": "string",
                        "description": "What this account calls it, falling back to the model."
                    },
                    "model": {
                        "type": "string",
                        "example": "Pixel 7a"
                    },
                    "androidVersion": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "appVersion": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "enabled": {
                        "type": "boolean",
                        "description": "Whether the pairing is live."
                    },
                    "lastSeenAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "The last time this handset polled us — a heartbeat, not the last time it sent something. The app polls about every ten seconds while it is running. Null means it has never polled."
                    },
                    "offlineFor": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Seconds since the last heartbeat. The app polls about every ten seconds, so anything under a minute is healthy."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "online",
                            "idle",
                            "offline",
                            "never"
                        ],
                        "description": "Derived from `offlineFor` against the installation's threshold. `never` means it has never polled."
                    },
                    "sims": {
                        "type": "array",
                        "description": "On the detail endpoint only.",
                        "items": {
                            "$ref": "#/components/schemas/Sim"
                        }
                    }
                },
                "required": [
                    "id",
                    "name",
                    "model",
                    "enabled",
                    "status"
                ]
            },
            "Sim": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "description": "The card's own id. This is what `PATCH /devices/{id}/sims/{simId}` renames, and it is deliberately not the slot: a slot is a tray, and swapping the card in it retires this record and creates another.",
                        "example": 14
                    },
                    "slot": {
                        "type": "integer",
                        "description": "What `simSlot` on a send names.",
                        "example": 0
                    },
                    "label": {
                        "type": "string",
                        "description": "What the panel calls this card: the operator's own name for it where one has been set, then the network name, falling back to the slot.",
                        "example": "GrameenPhone"
                    },
                    "operatorLabel": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The name a person gave this card in the panel, or null. Set, it is what `label` reads and it follows the card between trays and handsets.",
                        "example": "Robi-07 finance"
                    },
                    "name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "number": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "carrier": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "country": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "iccLastFour": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The last four digits of the ICCID, for matching a row against a card in your hand. The full ICCID is not stored.",
                        "example": "4471"
                    },
                    "identitySource": {
                        "type": "string",
                        "enum": [
                            "iccid",
                            "subscription",
                            "weak",
                            "slot"
                        ],
                        "description": "How confidently this card was recognised. `weak` cannot tell two cards from the same operator apart, and `slot` knows nothing but the tray."
                    },
                    "firstSeenAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When this card was first seen in this slot."
                    },
                    "removedAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When this card left this slot. Null while it is still in the phone; a card that has left is history and is never sent from."
                    },
                    "enabled": {
                        "type": "boolean"
                    }
                },
                "required": [
                    "slot",
                    "label",
                    "identitySource",
                    "enabled"
                ]
            },
            "SimRenamed": {
                "allOf": [
                    {
                        "$ref": "#/components/schemas/Sim"
                    },
                    {
                        "type": "object",
                        "properties": {
                            "renamed": {
                                "type": "integer",
                                "description": "How many rows the name reached — at least 1. The name belongs to the CARD, so it spreads to every slot on this account holding the same card: rename a SIM you have moved between two handsets and both rows change. A card recognised only weakly cannot be tracked between trays and always answers 1.",
                                "example": 1
                            }
                        },
                        "required": [
                            "renamed"
                        ]
                    }
                ]
            },
            "ConnectExchange": {
                "type": "object",
                "description": "The credential, once. Nothing can return `apiKey` again — we store only a SHA-256 of it — so write it down before you answer the request that triggered this.",
                "properties": {
                    "apiKey": {
                        "type": "string",
                        "example": "sboxk_live_U6ozbQFOdEQ…",
                        "description": "Send it as `Authorization: Bearer <key>`. **Returned exactly once.**"
                    },
                    "key": {
                        "type": "object",
                        "description": "What the credential is, for display. `prefix` is the part that stays visible in the panel, so it is what to show a merchant asking which key their site is using.",
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "name": {
                                "type": "string",
                                "example": "My Shop (https://shop.example.com)"
                            },
                            "prefix": {
                                "type": "string",
                                "example": "sboxk_live_U6ozbQFO"
                            },
                            "scopes": {
                                "type": "array",
                                "items": "string"
                            },
                            "siteUrl": {
                                "type": "string",
                                "example": "https://shop.example.com"
                            }
                        }
                    },
                    "account": {
                        "type": "object",
                        "description": "Who the key belongs to. Everything else about the account — credits, limits, the announcement — is on `GET /account`.",
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "name": {
                                "type": "string"
                            },
                            "email": {
                                "type": "string",
                                "format": "email"
                            },
                            "timeZone": {
                                "type": "string",
                                "example": "Europe/London"
                            }
                        }
                    },
                    "devicesPaired": {
                        "type": "integer",
                        "description": "**Read this before showing a send screen.** A connected account works and can send nothing at all until an Android handset is paired, and there is no API that can pair one — it is a QR code the app scans. Zero here means the merchant's first screen should be the pairing instructions, not a dashboard whose every button will fail."
                    }
                }
            },
            "Preferences": {
                "type": "object",
                "description": "The account settings that govern a send. Read only — every one of these is changed in the panel, on `preferences.php`.",
                "properties": {
                    "delay": {
                        "type": "object",
                        "description": "The gap the handset leaves between two messages of one campaign. A campaign may override it; this is the account default. `maxSeconds` is always a number — a fixed delay answers the same value twice rather than a null — so a client can multiply it by a recipient count to say how long a batch will take.",
                        "properties": {
                            "raw": {
                                "type": "string",
                                "example": "20-40"
                            },
                            "minSeconds": {
                                "type": "integer",
                                "example": 20
                            },
                            "maxSeconds": {
                                "type": "integer",
                                "example": 40
                            }
                        }
                    },
                    "ussdDelay": {
                        "type": "object",
                        "description": "The same shape, for `POST /ussd`.",
                        "properties": {
                            "raw": {
                                "type": "string",
                                "example": "5"
                            },
                            "minSeconds": {
                                "type": "integer",
                                "example": 5
                            },
                            "maxSeconds": {
                                "type": "integer",
                                "example": 5
                            }
                        }
                    },
                    "sleepTime": {
                        "type": "object",
                        "description": "The hours the handset will not send in, in the ACCOUNT HOLDER'S timezone — `GET /account` publishes it as `timeZone`. A message accepted inside this window is queued and goes out when it closes, which is not a failure and will not report as one. Every key is present in both shapes: switched off, `enabled` is false and the two times are null.",
                        "properties": {
                            "enabled": {
                                "type": "boolean"
                            },
                            "from": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "example": "22:00"
                            },
                            "to": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "example": "08:00"
                            }
                        }
                    },
                    "reportDelivery": {
                        "type": "boolean",
                        "description": "Whether the handset is asked for delivery receipts. **False means no message on this account will ever reach `Delivered`** and no `message.delivered` webhook will ever fire — `Sent` is the last status there is. A client showing a delivery column should say so rather than leave it permanently empty."
                    },
                    "autoRetry": {
                        "type": "boolean",
                        "description": "Whether a failed message is retried automatically, up to the install's own limit. False means a `message.failed` is final unless something asks for `POST /messages/{id}/resend`."
                    },
                    "strictNumbers": {
                        "type": "boolean",
                        "description": "Off for almost every account. On, a recipient the number checker cannot defend is REFUSED rather than warned about — the only setting here that can turn an accepted send into a `400`. A bare local number with no country is untouched either way, because it never earns a warning in the first place."
                    }
                }
            },
            "Subscription": {
                "type": "object",
                "description": "What this account is on, and how the term ends. Every key is present in both shapes — a free account has a null `plan`, not a missing one — so there is no need to check which shape arrived before reading it.",
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": [
                            "FREE",
                            "ACTIVE",
                            "CANCELLED"
                        ],
                        "description": "Only three values reach this field, because it reports the CURRENT term rather than the newest row. `FREE` is an account that has never bought anything, or whose last term has run out — and it is a normal working account: it has credits, it has allowances and it can send. `ACTIVE` is a live subscription. `CANCELLED` has been stopped at the gateway and still runs to `expiresAt` — the customer keeps the plan and will not be charged again, so `renews` is false. An expired or erased subscription is simply not current, and reads as `FREE`."
                    },
                    "plan": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "description": "Null on the free tier. Allowances are NOT here: credits and the device and contact limits live on `GET /account`, read off the account itself, because an operator can raise them for one customer without moving them for everybody on the plan.",
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "name": {
                                "type": "string",
                                "example": "Business"
                            },
                            "price": {
                                "type": "number",
                                "format": "float",
                                "example": 29
                            },
                            "currency": {
                                "type": "string",
                                "example": "USD"
                            },
                            "interval": {
                                "type": "object",
                                "description": "The billing period, as a unit and a count rather than a word — `{\"unit\": \"MONTH\", \"count\": 3}` is a real plan and \"quarterly\" is a word somebody would have to parse.",
                                "properties": {
                                    "unit": {
                                        "type": "string",
                                        "enum": [
                                            "DAY",
                                            "WEEK",
                                            "MONTH",
                                            "YEAR"
                                        ]
                                    },
                                    "count": {
                                        "type": "integer",
                                        "example": 1
                                    }
                                }
                            }
                        }
                    },
                    "startedAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When this subscription began. Null on the free tier."
                    },
                    "expiresAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "The end of the current term — the renewal date where `renews` is true, and the day sending stops where it is false. Null means there is no end date, which is what an installation with no plans means."
                    },
                    "renews": {
                        "type": "boolean",
                        "description": "**Read this rather than working it out from `paymentMethod`.** It is the difference between \"renews on the 5th\" and \"expires on the 5th\", which is one date and two very different sentences. False on the free tier, on any prepaid term, on a `CANCELLED` one that is still running, and on an `ACTIVE` one the customer has cancelled — a cancellation takes effect at the end of the paid period, so the status stays `ACTIVE` until `expiresAt` and this is the only field that says the term is the last one."
                    },
                    "prepaid": {
                        "type": "boolean",
                        "description": "Bought one period at a time, with nothing on file to charge again — which is what paying in cryptocurrency means. A prepaid term ends unless the customer buys another one."
                    },
                    "paymentMethod": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The gateway that billed it. Null on the free tier.",
                        "example": "PayPal"
                    },
                    "cyclesCompleted": {
                        "type": "integer",
                        "description": "How many periods have been billed so far."
                    },
                    "totalCycles": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The cycle cap where the plan has one, and null for the ordinary case of a subscription that runs until somebody cancels it. Null rather than 0, so that a client rendering \"2 of N\" cannot print \"2 of 0\"."
                    },
                    "reference": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The gateway's own id for this subscription — the identifier to quote to support, and the one to search a gateway dashboard by. Null on the free tier and on a prepaid term, where there is no subscription at the gateway to point at."
                    }
                },
                "required": [
                    "status",
                    "plan",
                    "renews",
                    "prepaid",
                    "cyclesCompleted"
                ]
            },
            "DevicePairing": {
                "type": "object",
                "description": "An invitation for one handset to join this account. It is not a device and it never becomes one on its own — the phone has to scan it.\n\nThere is no allowance count here on purpose: `GET /account` answers how many devices this plan allows and how many are in use. This route answers with a `409 devices_limit_reached` when there is no room, which is the same fact in the form you can act on.",
                "properties": {
                    "id": {
                        "type": "integer",
                        "example": 41,
                        "description": "Poll `GET /devices/pairing/{id}` with this."
                    },
                    "payload": {
                        "type": "string",
                        "example": "{\"key\":\"3f9a…\"}",
                        "description": "The exact string the QR must encode. Use it verbatim if you are rendering your own picture — do not rebuild the JSON by hand."
                    },
                    "qr": {
                        "type": "string",
                        "format": "uri",
                        "example": "data:image/png;base64,iVBORw0KGgo…",
                        "description": "The same thing already drawn, as a PNG data URI. Goes straight into an `<img src>`, so you need neither a QR library nor — and this is the point — a third-party QR service you would be sending the code to."
                    },
                    "expiresAt": {
                        "type": "string",
                        "format": "date-time",
                        "description": "15 minutes out. After this the code pairs nothing; ask for another."
                    },
                    "appUrl": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "uri",
                        "description": "Where this installation publishes the Android app. Scanning is the last step; installing it is the one before."
                    }
                },
                "required": [
                    "id",
                    "payload",
                    "qr",
                    "expiresAt"
                ]
            },
            "DevicePairingStatus": {
                "type": "object",
                "description": "Where an invitation is in its short life.",
                "properties": {
                    "id": {
                        "type": "integer",
                        "example": 41
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "pending",
                            "claimed",
                            "expired"
                        ],
                        "description": "`pending` — live and unused. `claimed` — a handset paired, and `device` is it. `expired` — the window closed, or you cancelled it. A code claimed just before it lapsed reads `claimed`, not `expired`."
                    },
                    "expiresAt": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "claimedAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "device": {
                        "allOf": [
                            {
                                "$ref": "#/components/schemas/Device"
                            }
                        ],
                        "nullable": true,
                        "description": "The handset that paired, once one has. Null while pending."
                    }
                },
                "required": [
                    "id",
                    "status",
                    "expiresAt"
                ]
            },
            "DevicePage": {
                "type": "object",
                "description": "One page of devices, newest first.\n\nPagination is by cursor and not by offset: pass this page's `nextCursor` back as `startingAfter` to get the next one, and stop when `hasMore` is false. Do not build a cursor yourself and do not assume it is an id — it is opaque and the only valid source for one is a previous page.",
                "properties": {
                    "data": {
                        "type": "array",
                        "description": "The devices on this page, ordered by id descending, so the most recent is first. An empty array is an ordinary answer and not an error — it is what a filter that matches nothing returns.",
                        "items": {
                            "$ref": "#/components/schemas/Device"
                        }
                    },
                    "hasMore": {
                        "type": "boolean",
                        "description": "Whether another page exists. It is an observation and not an estimate — one row past the page is read to answer it — so `false` means there is genuinely nothing after this, and a caller may stop without a final empty request."
                    },
                    "nextCursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Pass as `startingAfter` for the next page. Null exactly when `hasMore` is false, so either one may be used as the loop condition — they cannot disagree."
                    }
                },
                "required": [
                    "data",
                    "hasMore",
                    "nextCursor"
                ]
            },
            "ContactList": {
                "type": "object",
                "description": "A named group of contacts, and what `POST /campaigns` sends to.",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "string"
                    },
                    "contacts": {
                        "type": "integer",
                        "description": "Everyone on the list, unsubscribed people included. Compare with `subscribed` before quoting a reach."
                    },
                    "subscribed": {
                        "type": "integer",
                        "description": "How many of them a send would actually reach."
                    }
                },
                "required": [
                    "id",
                    "name"
                ]
            },
            "ContactListPage": {
                "type": "object",
                "description": "One page of contact lists, newest first.\n\nPagination is by cursor and not by offset: pass this page's `nextCursor` back as `startingAfter` to get the next one, and stop when `hasMore` is false. Do not build a cursor yourself and do not assume it is an id — it is opaque and the only valid source for one is a previous page.",
                "properties": {
                    "data": {
                        "type": "array",
                        "description": "The contact lists on this page, ordered by id descending, so the most recent is first. An empty array is an ordinary answer and not an error — it is what a filter that matches nothing returns.",
                        "items": {
                            "$ref": "#/components/schemas/ContactList"
                        }
                    },
                    "hasMore": {
                        "type": "boolean",
                        "description": "Whether another page exists. It is an observation and not an estimate — one row past the page is read to answer it — so `false` means there is genuinely nothing after this, and a caller may stop without a final empty request."
                    },
                    "nextCursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Pass as `startingAfter` for the next page. Null exactly when `hasMore` is false, so either one may be used as the loop condition — they cannot disagree."
                    }
                },
                "required": [
                    "data",
                    "hasMore",
                    "nextCursor"
                ]
            },
            "Contact": {
                "type": "object",
                "description": "One person on one list. A contact belongs to exactly one list — the same number on three lists is three contacts with three ids, and unsubscribing one leaves the other two subscribed. If you need a suppression that spans everything, that is the blacklist and not this.",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "number": {
                        "type": "string",
                        "example": "+11234567890",
                        "description": "Stored exactly as it was given. Nothing normalises it after the fact, so `+447700900123` and `447700900123` are two contacts — send `defaultCountry` when you create them if you want one form."
                    },
                    "listId": {
                        "type": "integer"
                    },
                    "listName": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The list's name, denormalised onto the row so a table can be drawn without a second request per contact."
                    },
                    "subscribed": {
                        "type": "boolean",
                        "description": "False and every send skips them. This is how an opt-out is recorded — deleting the contact would let the next import undo it."
                    }
                },
                "required": [
                    "id",
                    "number",
                    "listId",
                    "subscribed"
                ]
            },
            "ContactPage": {
                "type": "object",
                "description": "One page of contacts, newest first.\n\nPagination is by cursor and not by offset: pass this page's `nextCursor` back as `startingAfter` to get the next one, and stop when `hasMore` is false. Do not build a cursor yourself and do not assume it is an id — it is opaque and the only valid source for one is a previous page.",
                "properties": {
                    "data": {
                        "type": "array",
                        "description": "The contacts on this page, ordered by id descending, so the most recent is first. An empty array is an ordinary answer and not an error — it is what a filter that matches nothing returns.",
                        "items": {
                            "$ref": "#/components/schemas/Contact"
                        }
                    },
                    "hasMore": {
                        "type": "boolean",
                        "description": "Whether another page exists. It is an observation and not an estimate — one row past the page is read to answer it — so `false` means there is genuinely nothing after this, and a caller may stop without a final empty request."
                    },
                    "nextCursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Pass as `startingAfter` for the next page. Null exactly when `hasMore` is false, so either one may be used as the loop condition — they cannot disagree."
                    }
                },
                "required": [
                    "data",
                    "hasMore",
                    "nextCursor"
                ]
            },
            "ContactBulkResult": {
                "type": "object",
                "description": "The outcome of a bulk write, one entry per row you sent. The status is 200 even when some rows failed — one bad number does not discard the rest.",
                "properties": {
                    "listId": {
                        "type": "integer"
                    },
                    "created": {
                        "type": "integer"
                    },
                    "updated": {
                        "type": "integer",
                        "description": "Rows whose name or subscription actually moved. A re-posted row that matches what is stored is `skipped`, not updated."
                    },
                    "skipped": {
                        "type": "integer",
                        "description": "Duplicates that needed no write — either because nothing changed, or because `onDuplicate` was `skip`."
                    },
                    "failed": {
                        "type": "integer"
                    },
                    "truncated": {
                        "type": "boolean",
                        "description": "Present and true when the account's contact limit was reached partway through. The rows beyond it are `failed` with `contacts_limit_reached`; everything before it was written."
                    },
                    "results": {
                        "type": "array",
                        "description": "In the order you sent them, so `results[i]` answers `contacts[i]`.",
                        "items": {
                            "$ref": "#/components/schemas/ContactBulkRow"
                        }
                    }
                },
                "required": [
                    "listId",
                    "created",
                    "updated",
                    "skipped",
                    "failed",
                    "results"
                ]
            },
            "ContactBulkRow": {
                "type": "object",
                "description": "What happened to one row of a bulk write.",
                "properties": {
                    "index": {
                        "type": "integer",
                        "description": "The position in the `contacts` array you sent."
                    },
                    "outcome": {
                        "type": "string",
                        "enum": [
                            "created",
                            "updated",
                            "skipped",
                            "failed"
                        ],
                        "description": "- `created` — a new contact. No `contact.subscribed` event is emitted for one; a creation is not a subscription change.\n- `updated` — an existing contact, and at least one field moved.\n- `skipped` — an existing contact, and **nothing changed**: no write, no event. This is what makes a nightly full sync cheap, so send your whole customer base rather than working out the delta yourself.\n- `failed` — `code` and `message` say why. The other rows still went through: one bad number does not discard the request."
                    },
                    "id": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The contact id, on anything but a failure."
                    },
                    "code": {
                        "type": "string",
                        "enum": [
                            "invalid_number",
                            "contact_exists",
                            "contacts_limit_reached"
                        ],
                        "description": "On a failure only. `contact_exists` appears only when `onDuplicate` was `error`."
                    },
                    "message": {
                        "type": "string",
                        "description": "On a failure only. A sentence for a human reading a log."
                    }
                },
                "required": [
                    "index",
                    "outcome"
                ]
            },
            "BlacklistEntry": {
                "type": "object",
                "description": "One suppressed number. Every send this account makes is filtered against these.",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "number": {
                        "type": "string",
                        "example": "+11234567890",
                        "description": "Exactly as it was stored. Suppression is an exact string match, so this is the form that is actually blocked — a number sent in any other form is not."
                    }
                },
                "required": [
                    "id",
                    "number"
                ]
            },
            "BlacklistPage": {
                "type": "object",
                "description": "One page of suppressed numbers, newest first.\n\nPagination is by cursor and not by offset: pass this page's `nextCursor` back as `startingAfter` to get the next one, and stop when `hasMore` is false. Do not build a cursor yourself and do not assume it is an id — it is opaque and the only valid source for one is a previous page.",
                "properties": {
                    "data": {
                        "type": "array",
                        "description": "The suppressed numbers on this page, ordered by id descending, so the most recent is first. An empty array is an ordinary answer and not an error — it is what a filter that matches nothing returns.",
                        "items": {
                            "$ref": "#/components/schemas/BlacklistEntry"
                        }
                    },
                    "hasMore": {
                        "type": "boolean",
                        "description": "Whether another page exists. It is an observation and not an estimate — one row past the page is read to answer it — so `false` means there is genuinely nothing after this, and a caller may stop without a final empty request."
                    },
                    "nextCursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Pass as `startingAfter` for the next page. Null exactly when `hasMore` is false, so either one may be used as the loop condition — they cannot disagree."
                    }
                },
                "required": [
                    "data",
                    "hasMore",
                    "nextCursor"
                ]
            },
            "Template": {
                "type": "object",
                "description": "A message body the customer wrote in the panel.",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "string",
                        "example": "Shipping notice"
                    },
                    "text": {
                        "type": "string",
                        "example": "Hi %name%, your order has shipped.",
                        "description": "Verbatim, unescaped. Placeholders are not substituted here — the send path fills them in against a contact, so substitute what you know and post the result."
                    }
                },
                "required": [
                    "id",
                    "name",
                    "text"
                ]
            },
            "TemplatePage": {
                "type": "object",
                "description": "One page of templates, newest first.\n\nPagination is by cursor and not by offset: pass this page's `nextCursor` back as `startingAfter` to get the next one, and stop when `hasMore` is false. Do not build a cursor yourself and do not assume it is an id — it is opaque and the only valid source for one is a previous page.",
                "properties": {
                    "data": {
                        "type": "array",
                        "description": "The templates on this page, ordered by id descending, so the most recent is first. An empty array is an ordinary answer and not an error — it is what a filter that matches nothing returns.",
                        "items": {
                            "$ref": "#/components/schemas/Template"
                        }
                    },
                    "hasMore": {
                        "type": "boolean",
                        "description": "Whether another page exists. It is an observation and not an estimate — one row past the page is read to answer it — so `false` means there is genuinely nothing after this, and a caller may stop without a final empty request."
                    },
                    "nextCursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Pass as `startingAfter` for the next page. Null exactly when `hasMore` is false, so either one may be used as the loop condition — they cannot disagree."
                    }
                },
                "required": [
                    "data",
                    "hasMore",
                    "nextCursor"
                ]
            },
            "Ussd": {
                "type": "object",
                "description": "A code dialled on a handset. The answer arrives seconds later — poll this resource or subscribe to `ussd.response`.",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "request": {
                        "type": "string",
                        "example": "*123#"
                    },
                    "response": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "deviceId": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "simSlot": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The SIM slot the request was addressed to. Null means the device's own default."
                    },
                    "sentSimSlot": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The SIM slot the handset actually dialled from. Null for a request answered by an app build that does not report it."
                    },
                    "sentSim": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "description": "The card that answered, as it was at the moment of dialling. `label` is a value copy and stays correct after the card has been removed from the phone; `id` is null when the card could not be identified.",
                        "properties": {
                            "id": {
                                "type": [
                                    "integer",
                                    "null"
                                ]
                            },
                            "label": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "example": "GrameenPhone"
                            }
                        }
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "Pending",
                            "Received"
                        ],
                        "description": "`Pending` until the handset reports back — `response` is null until then. There is no failure status: a code the network never answers simply stays `Pending`, so time out on your side rather than waiting for one."
                    },
                    "sentAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "respondedAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                },
                "required": [
                    "id",
                    "request",
                    "status"
                ]
            },
            "UssdPage": {
                "type": "object",
                "description": "One page of USSD requests, newest first.\n\nPagination is by cursor and not by offset: pass this page's `nextCursor` back as `startingAfter` to get the next one, and stop when `hasMore` is false. Do not build a cursor yourself and do not assume it is an id — it is opaque and the only valid source for one is a previous page.",
                "properties": {
                    "data": {
                        "type": "array",
                        "description": "The USSD requests on this page, ordered by id descending, so the most recent is first. An empty array is an ordinary answer and not an error — it is what a filter that matches nothing returns.",
                        "items": {
                            "$ref": "#/components/schemas/Ussd"
                        }
                    },
                    "hasMore": {
                        "type": "boolean",
                        "description": "Whether another page exists. It is an observation and not an estimate — one row past the page is read to answer it — so `false` means there is genuinely nothing after this, and a caller may stop without a final empty request."
                    },
                    "nextCursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Pass as `startingAfter` for the next page. Null exactly when `hasMore` is false, so either one may be used as the loop condition — they cannot disagree."
                    }
                },
                "required": [
                    "data",
                    "hasMore",
                    "nextCursor"
                ]
            },
            "Account": {
                "type": "object",
                "description": "Null means unlimited, throughout.",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "string"
                    },
                    "email": {
                        "type": "string",
                        "format": "email"
                    },
                    "timeZone": {
                        "type": "string",
                        "example": "Europe/London"
                    },
                    "expiresAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When the subscription lapses. Past it, sends answer 403."
                    },
                    "credits": {
                        "type": "object",
                        "description": "What is left to spend. **Check `unlimited` before reading `remaining`** — on an unlimited account `remaining` is null, and a client that renders it as a number shows `0` to the customer with the fewest restrictions on the installation.",
                        "properties": {
                            "remaining": {
                                "type": [
                                    "integer",
                                    "null"
                                ],
                                "description": "Credits left, or null when there is no ceiling. Not the same as a message count: on most installations a long message costs one credit per part, which is what `POST /messages/preview` answers before you commit."
                            },
                            "unlimited": {
                                "type": "boolean"
                            }
                        }
                    },
                    "limits": {
                        "type": "object",
                        "properties": {
                            "devices": {
                                "$ref": "#/components/schemas/Limit"
                            },
                            "contacts": {
                                "$ref": "#/components/schemas/Limit"
                            }
                        }
                    },
                    "apiKey": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "description": "Which credential made this request.",
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "name": {
                                "type": "string"
                            },
                            "prefix": {
                                "type": "string",
                                "example": "sboxk_live_a1b2c3d4"
                            },
                            "scopes": {
                                "type": "array",
                                "items": {
                                    "type": "string",
                                    "enum": [
                                        "read",
                                        "write"
                                    ]
                                },
                                "description": "What this key may do. A GET needs `read`; every other method needs `write`.",
                                "example": [
                                    "read",
                                    "write"
                                ]
                            }
                        }
                    },
                    "announcement": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "description": "What the operator of this installation is currently announcing to everybody, or null when there is nothing. It rides on this endpoint rather than having one of its own because a client showing it is already asking for credits on the same screen, and a second request for a string that changes twice a year is a request nobody should pay for.\n\n**`html` IS HTML AND IT IS NOT SANITISED.** It is written by the person who runs this installation, in their own rich-text editor, and the panel renders it as markup on purpose — there, the author and the reader are the same person. Wherever that is not true, and it is not true in your application, escape it or run it through an allow-list before it reaches a DOM.",
                        "properties": {
                            "html": {
                                "type": "string",
                                "example": "<p>Maintenance on Sunday 02:00 UTC.</p>"
                            },
                            "hash": {
                                "type": "string",
                                "description": "SHA-256 of `html`. It exists so a notice can be dismissed without being forgotten: store the hash you hid, and show the notice again when it no longer matches. Nothing is recorded on our side about who has read what.",
                                "example": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
                            }
                        },
                        "required": [
                            "html",
                            "hash"
                        ]
                    }
                },
                "required": [
                    "id",
                    "name",
                    "email",
                    "credits",
                    "limits"
                ]
            },
            "Limit": {
                "type": "object",
                "properties": {
                    "limit": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Null is unlimited."
                    },
                    "used": {
                        "type": "integer"
                    }
                },
                "required": [
                    "limit",
                    "used"
                ]
            },
            "Webhook": {
                "type": "object",
                "description": "One endpoint of yours, and its health. An endpoint that has switched itself off keeps its row and its subscriptions — turn it back on with `PATCH /webhooks/{id}` once the receiver is fixed.",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "name": {
                        "type": "string"
                    },
                    "url": {
                        "type": "string",
                        "format": "uri"
                    },
                    "format": {
                        "type": "string",
                        "enum": [
                            "json",
                            "xml"
                        ]
                    },
                    "events": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "`[\"*\"]` means every event, including ones added later."
                    },
                    "active": {
                        "type": "boolean",
                        "description": "False and nothing is delivered here. It is false either because somebody switched it off or because 20 consecutive failures did — `disabledReason` says which."
                    },
                    "failureStreak": {
                        "type": "integer",
                        "description": "Consecutive failures. At 20 the endpoint switches itself off."
                    },
                    "disabledReason": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Why it is off, when we turned it off ourselves. Null when it is active, and null when a person switched it off."
                    },
                    "lastDeliveryAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time",
                        "description": "When we last attempted a delivery here. Null on an endpoint that has never had an event to send — which is not the same as broken, and is the ordinary state of one subscribed to a rare event."
                    },
                    "lastStatusCode": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "The HTTP status your endpoint answered last time. Null when the attempt never got an answer at all — a timeout, DNS, a refused connection — which is a different fault from a 500 and worth showing differently."
                    },
                    "createdAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "secret": {
                        "type": "string",
                        "description": "**Returned once**, on create and on rotate. There is no read path — lose it and you rotate. Sign-check with it per the docs."
                    },
                    "previousSecretValidForSeconds": {
                        "type": "integer",
                        "description": "On rotate: how long the old secret keeps signing too, so a receiver can be redeployed without dropping events."
                    }
                },
                "required": [
                    "id",
                    "name",
                    "url",
                    "format",
                    "events",
                    "active"
                ]
            },
            "WebhookPage": {
                "type": "object",
                "description": "One page of endpoints, newest first.\n\nPagination is by cursor and not by offset: pass this page's `nextCursor` back as `startingAfter` to get the next one, and stop when `hasMore` is false. Do not build a cursor yourself and do not assume it is an id — it is opaque and the only valid source for one is a previous page.",
                "properties": {
                    "data": {
                        "type": "array",
                        "description": "The endpoints on this page, ordered by id descending, so the most recent is first. An empty array is an ordinary answer and not an error — it is what a filter that matches nothing returns.",
                        "items": {
                            "$ref": "#/components/schemas/Webhook"
                        }
                    },
                    "hasMore": {
                        "type": "boolean",
                        "description": "Whether another page exists. It is an observation and not an estimate — one row past the page is read to answer it — so `false` means there is genuinely nothing after this, and a caller may stop without a final empty request."
                    },
                    "nextCursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Pass as `startingAfter` for the next page. Null exactly when `hasMore` is false, so either one may be used as the loop condition — they cannot disagree."
                    }
                },
                "required": [
                    "data",
                    "hasMore",
                    "nextCursor"
                ]
            },
            "WebhookDelivery": {
                "type": "object",
                "description": "One attempt to hand one event to one endpoint. Retries are new attempts on the same row; a replay is a new row.",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "webhookId": {
                        "type": "integer"
                    },
                    "eventId": {
                        "type": "string",
                        "example": "evt_01K2F8QW3N4RXB7M",
                        "description": "Also the `X-SmsGateway-Event-Id` header. Deduplicate on it — a retry and a replay both carry the original."
                    },
                    "event": {
                        "type": "string",
                        "example": "message.delivered"
                    },
                    "format": {
                        "type": "string",
                        "enum": [
                            "json",
                            "xml"
                        ]
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "Queued",
                            "Delivering",
                            "Delivered",
                            "Failed",
                            "Expired"
                        ],
                        "description": "- `Queued` — waiting for a worker.\n- `Delivering` — a worker is making the request now.\n- `Delivered` — your endpoint answered 2xx. Terminal.\n- `Failed` — this attempt failed and another is scheduled. **Not terminal**, and not a lost event.\n- `Expired` — all six attempts failed over about eight hours. Terminal, and the only status that means the event never arrived. Replay it with `POST /webhooks/{id}/deliveries/{deliveryId}/replay` once the receiver is fixed."
                    },
                    "attempt": {
                        "type": "integer"
                    },
                    "statusCode": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "error": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "durationMs": {
                        "type": [
                            "integer",
                            "null"
                        ]
                    },
                    "payload": {
                        "type": "string",
                        "description": "The exact bytes we signed."
                    },
                    "responseBody": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Truncated."
                    },
                    "nextAttemptAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "deliveredAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "createdAt": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                },
                "required": [
                    "id",
                    "webhookId",
                    "eventId",
                    "event",
                    "status",
                    "attempt"
                ]
            },
            "WebhookDeliveryPage": {
                "type": "object",
                "description": "One page of delivery attempts, newest first.\n\nPagination is by cursor and not by offset: pass this page's `nextCursor` back as `startingAfter` to get the next one, and stop when `hasMore` is false. Do not build a cursor yourself and do not assume it is an id — it is opaque and the only valid source for one is a previous page.",
                "properties": {
                    "data": {
                        "type": "array",
                        "description": "The delivery attempts on this page, ordered by id descending, so the most recent is first. An empty array is an ordinary answer and not an error — it is what a filter that matches nothing returns.",
                        "items": {
                            "$ref": "#/components/schemas/WebhookDelivery"
                        }
                    },
                    "hasMore": {
                        "type": "boolean",
                        "description": "Whether another page exists. It is an observation and not an estimate — one row past the page is read to answer it — so `false` means there is genuinely nothing after this, and a caller may stop without a final empty request."
                    },
                    "nextCursor": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Pass as `startingAfter` for the next page. Null exactly when `hasMore` is false, so either one may be used as the loop condition — they cannot disagree."
                    }
                },
                "required": [
                    "data",
                    "hasMore",
                    "nextCursor"
                ]
            },
            "EventCatalogue": {
                "type": "object",
                "description": "Every event that can be subscribed to, grouped by resource, with the sample payload each one sends.",
                "properties": {
                    "data": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "resource": {
                                    "type": "string",
                                    "example": "message"
                                },
                                "events": {
                                    "type": "array",
                                    "items": {
                                        "type": "object",
                                        "properties": {
                                            "type": {
                                                "type": "string",
                                                "example": "message.delivered"
                                            },
                                            "summary": {
                                                "type": "string"
                                            },
                                            "sample": {
                                                "type": "object"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "apiVersion": {
                        "type": "string",
                        "example": "2026-08-12"
                    }
                },
                "required": [
                    "data",
                    "apiVersion"
                ]
            },
            "Error": {
                "type": "object",
                "description": "Every failure, in one shape. The status code is the first thing to branch on; `code` is the specific reason.",
                "properties": {
                    "error": {
                        "type": "object",
                        "properties": {
                            "type": {
                                "type": "string",
                                "enum": [
                                    "invalid_request",
                                    "authentication_error",
                                    "permission_error",
                                    "not_found",
                                    "conflict",
                                    "rate_limit_error",
                                    "api_error"
                                ]
                            },
                            "code": {
                                "type": "string",
                                "example": "missing_parameter"
                            },
                            "message": {
                                "type": "string",
                                "description": "A sentence for a human reading a log. Stable in meaning, not in wording — branch on `code`, not on this."
                            },
                            "param": {
                                "type": "string",
                                "description": "The request field at fault, when there is one."
                            }
                        },
                        "required": [
                            "type",
                            "code",
                            "message"
                        ]
                    },
                    "requestId": {
                        "type": "string",
                        "description": "Also on the `X-Request-Id` response header and in our logs. Quote it and \"it failed at 14:03\" becomes answerable.",
                        "example": "req_01K2F8QW3N4RXB7M"
                    }
                },
                "required": [
                    "error"
                ]
            }
        }
    }
}