{
  "openapi": "3.0.3",
  "info": {
    "title": "WebSMS API",
    "description": "SMS API for sending and receiving text messages. Integrate with Salesforce, HubSpot, Zapier, Shopify, Zoho CRM, and more.",
    "version": "1.0.0",
    "contact": {
      "name": "WebSMS Support",
      "email": "support@websms.co.nz"
    }
  },
  "servers": [
    {
      "url": "https://api.websms.co.nz/api",
      "description": "Production (preferred)"
    },
    {
      "url": "https://websms.co.nz/api",
      "description": "Production (legacy host, also supported)"
    }
  ],
  "tags": [
    {
      "name": "Authentication",
      "description": "OAuth2-style API key authentication"
    },
    {
      "name": "SMS",
      "description": "Send SMS messages"
    },
    {
      "name": "Number Validation",
      "description": "Phone number validation and IPMS carrier lookup"
    },
    {
      "name": "Connexus",
      "description": "Connexus/Bulletin Connect compatible API - drop-in replacement for Bulletin Connect"
    },
    {
      "name": "Salesforce",
      "description": "Salesforce CRM integration"
    },
    {
      "name": "HubSpot",
      "description": "HubSpot CRM integration"
    },
    {
      "name": "Zapier",
      "description": "Zapier workflow integration"
    },
    {
      "name": "Shopify",
      "description": "Shopify e-commerce integration"
    },
    {
      "name": "Zoho",
      "description": "Zoho CRM integration"
    },
    {
      "name": "Outlook",
      "description": "Microsoft Outlook add-in (taskpane). Adds a 'New SMS' button to the Outlook ribbon."
    }
  ],
  "paths": {
    "/connexus/auth/token": {
      "post": {
        "tags": [
          "Authentication",
          "Connexus"
        ],
        "summary": "Get Access Token",
        "description": "Exchange API key credentials (client_id + client_secret) for a Bearer access token. Tokens are valid for 24 hours.",
        "operationId": "getAccessToken",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token issued successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing client_id or client_secret",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid client credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/send.php": {
      "get": {
        "tags": [
          "SMS"
        ],
        "summary": "Send SMS (Legacy GET)",
        "description": "Send an SMS message using query parameters. This is the legacy API endpoint.",
        "operationId": "sendSmsLegacy",
        "parameters": [
          {
            "name": "username",
            "in": "query",
            "required": true,
            "description": "Your WebSMS username",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "password",
            "in": "query",
            "required": true,
            "description": "Your WebSMS password or API key",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cellnum",
            "in": "query",
            "required": true,
            "description": "Recipient phone number (e.g., 6421234567)",
            "schema": {
              "type": "string",
              "example": "6421234567"
            }
          },
          {
            "name": "message",
            "in": "query",
            "required": true,
            "description": "SMS message content",
            "schema": {
              "type": "string",
              "example": "Hello from WebSMS!"
            }
          },
          {
            "name": "sender",
            "in": "query",
            "required": false,
            "description": "Sender ID (shortcode or alphanumeric)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "SMS sent successfully",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "example": "SUCCESS:12345"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request parameters"
          },
          "401": {
            "description": "Authentication failed"
          }
        }
      }
    },
    "/connexus/number/lookup": {
      "post": {
        "tags": [
          "Number Validation",
          "Connexus"
        ],
        "summary": "Phone Number Validation / IPMS Lookup",
        "description": "Validate a New Zealand mobile number and retrieve carrier information from the IPMS (Industry Portability Management System). Returns current carrier, portability status, and network details. Cost: $0.005 per lookup + GST.",
        "operationId": "connexusNumberLookup",
        "parameters": [
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account email (not needed if using Bearer token)",
            "schema": {
              "type": "string",
              "example": "user@domain.co.nz"
            }
          },
          {
            "name": "password",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account password (not needed if using Bearer token)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "number",
            "in": "query",
            "required": true,
            "description": "NZ mobile number to validate (local 02x or international 642x format)",
            "schema": {
              "type": "string",
              "example": "0211234567"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Number lookup successful",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NumberLookupResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid number format",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "tooShort": {
                    "summary": "Number too short",
                    "value": {
                      "success": false,
                      "error": "Invalid number",
                      "message": "Number too short. NZ mobile numbers must be 9-11 digits (e.g., 021234567)."
                    }
                  },
                  "tooLong": {
                    "summary": "Number too long",
                    "value": {
                      "success": false,
                      "error": "Invalid number",
                      "message": "Number too long. NZ mobile numbers must be 9-11 digits (e.g., 021234567)."
                    }
                  },
                  "invalidPrefix": {
                    "summary": "Invalid prefix",
                    "value": {
                      "success": false,
                      "error": "Invalid number",
                      "message": "Invalid prefix. NZ mobile numbers must start with 02X (e.g., 021, 022, 027)."
                    }
                  },
                  "invalidFormat": {
                    "summary": "Invalid format",
                    "value": {
                      "success": false,
                      "error": "Invalid number",
                      "message": "Invalid format. NZ mobile numbers must start with 02X (local) or 642X (international)."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient funds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/connexus/sms/out": {
      "post": {
        "tags": [
          "Connexus"
        ],
        "summary": "Send SMS",
        "description": "Send an SMS message to one or multiple recipients. For single recipient, use form-urlencoded with `to` as string. For multiple recipients, use JSON body with `to` as array. Supports Bearer token auth (recommended) or userId/password. For NZ numbers, response includes carrier/porting info.",
        "operationId": "connexusSendSms",
        "security": [
          {
            "bearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account email (not needed if using Bearer token)",
            "schema": {
              "type": "string",
              "example": "user@domain.co.nz"
            }
          },
          {
            "name": "password",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account password (not needed if using Bearer token)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Recipient phone number (international format). For single recipient via form-urlencoded. For multiple recipients, use JSON body instead.",
            "schema": {
              "type": "string",
              "example": "6421234567"
            }
          },
          {
            "name": "body",
            "in": "query",
            "required": true,
            "description": "SMS message content",
            "schema": {
              "type": "string",
              "example": "Hello from WebSMS!"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Sender ID (shortcode)",
            "schema": {
              "type": "string",
              "example": "2190"
            }
          },
          {
            "name": "messageClass",
            "in": "query",
            "required": true,
            "description": "Traffic classification for shortcode routing. Use `transactional` for OTP/2FA, confirmations, alerts, notifications and delivery updates; use `marketing` for promotional campaigns, offers and newsletters.",
            "schema": {
              "type": "string",
              "enum": [
                "transactional",
                "marketing"
              ],
              "example": "transactional"
            }
          },
          {
            "name": "messageId",
            "in": "query",
            "required": false,
            "description": "Custom message identifier (max 36 chars). Echoed back as customerMessageId in delivery-status webhooks and /connexus/sms/status/query responses, as replyToCustomerMessageId in incoming-reply webhooks, and usable as the messageId lookup parameter on /connexus/sms/status/query. Not returned in the send response itself — that returns our message_id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "rateCode",
            "in": "query",
            "required": false,
            "description": "Message source identifier for reporting",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fragmentationLimit",
            "in": "query",
            "required": false,
            "description": "Maximum SMS segments, 1-11 (or 0 for no limit of your own). 11 segments is 1683 characters. A longer body is trimmed to fit and the response then carries truncated: true. Messages are sent as GSM-7; characters outside ASCII (emoji, non-Latin scripts) are replaced with '?' and macronised/accented vowels are folded, so this endpoint does not send UCS-2/Unicode.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 11,
              "default": 11,
              "example": 11
            }
          },
          {
            "name": "sandbox",
            "in": "query",
            "required": false,
            "description": "Set to true to test without sending: no message is submitted and nothing is charged. The response echoes your message with its encoding (GSM-7/UCS-2), segment count and the price that would have been charged (sandbox_would_charge).",
            "schema": {
              "type": "boolean",
              "example": true
            }
          }
        ],
        "requestBody": {
          "description": "For sending to multiple recipients, use JSON body with `to` as an array",
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "to",
                  "body",
                  "messageClass"
                ],
                "properties": {
                  "to": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Array of recipient phone numbers",
                    "example": [
                      "6421111111",
                      "6422222222"
                    ]
                  },
                  "body": {
                    "type": "string",
                    "description": "SMS message content",
                    "example": "Hello from WebSMS!"
                  },
                  "messageClass": {
                    "type": "string",
                    "enum": [
                      "transactional",
                      "marketing"
                    ],
                    "description": "Traffic classification for shortcode routing. Use `transactional` for OTP/2FA, confirmations, alerts, notifications and delivery updates; use `marketing` for promotional campaigns, offers and newsletters.",
                    "example": "transactional"
                  },
                  "from": {
                    "type": "string",
                    "description": "Sender ID (shortcode)",
                    "example": "2190"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SMS accepted for delivery",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnexusSendResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing required parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Insufficient funds or unauthorized shortcode",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/connexus/sms/in": {
      "post": {
        "tags": [
          "Connexus"
        ],
        "summary": "Register Incoming SMS Webhook",
        "description": "Register a webhook URL to receive incoming SMS messages (MO - Mobile Originated).",
        "operationId": "connexusRegisterMoWebhook",
        "parameters": [
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account email (not needed if using Bearer token)",
            "schema": {
              "type": "string",
              "example": "user@domain.co.nz"
            }
          },
          {
            "name": "password",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account password (not needed if using Bearer token)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "Webhook URL to receive incoming SMS",
            "schema": {
              "type": "string",
              "example": "https://yoursite.com/incoming"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Webhook registered successfully"
          },
          "400": {
            "description": "Missing or invalid URL"
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/connexus/sms/status": {
      "post": {
        "tags": [
          "Connexus"
        ],
        "summary": "Register Delivery Status Webhook",
        "description": "Register a webhook URL to receive delivery status updates (DLR - Delivery Reports).",
        "operationId": "connexusRegisterDlrWebhook",
        "parameters": [
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account email (not needed if using Bearer token)",
            "schema": {
              "type": "string",
              "example": "user@domain.co.nz"
            }
          },
          {
            "name": "password",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account password (not needed if using Bearer token)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "url",
            "in": "query",
            "required": true,
            "description": "Webhook URL to receive delivery status updates",
            "schema": {
              "type": "string",
              "example": "https://yoursite.com/status"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Webhook registered successfully"
          },
          "400": {
            "description": "Missing or invalid URL"
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/connexus/sms/status/query": {
      "post": {
        "tags": [
          "Connexus"
        ],
        "summary": "Query Delivery Status",
        "description": "Look up delivery status of sent messages. Three modes: (1) by message_id — up to 100 comma-separated IDs returned by /connexus/sms/out, any message age; (2) by messageId — your own custom identifier supplied when sending, any message age; (3) list mode — browse the current calendar month with optional filters and pagination (max 100 per page). Supports Bearer token auth or userId/password.",
        "operationId": "connexusQueryStatus",
        "security": [
          {
            "bearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account email (not needed if using Bearer token)",
            "schema": {
              "type": "string",
              "example": "user@domain.co.nz"
            }
          },
          {
            "name": "password",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account password (not needed if using Bearer token)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "message_id",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of message IDs returned by /connexus/sms/out (max 100). No date restriction — works for any message age.",
            "schema": {
              "type": "string",
              "example": "1188236,1188240"
            }
          },
          {
            "name": "messageId",
            "in": "query",
            "required": false,
            "description": "Your own custom message identifier supplied when sending (max 100 matches returned). No date restriction.",
            "schema": {
              "type": "string",
              "example": "PRAC-042-7731"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "List mode: filter by recipient number (exact match)",
            "schema": {
              "type": "string",
              "example": "6421234567"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "List mode: filter by delivery status. PENDING also matches messages with no delivery report yet.",
            "schema": {
              "type": "string",
              "enum": [
                "DELIVRD",
                "UNDELIV",
                "ACCEPTD",
                "QUEUED",
                "FAILED",
                "PENDING",
                "BLOCKED",
                "DELETED",
                "UNKNOWN",
                "NACKED",
                "ENROUTE"
              ],
              "example": "UNDELIV"
            }
          },
          {
            "name": "start_date",
            "in": "query",
            "required": false,
            "description": "List mode: start of date range (Y-m-d or Y-m-d H:i:s). Must fall within the current calendar month.",
            "schema": {
              "type": "string",
              "example": "2026-08-01 09:00:00"
            }
          },
          {
            "name": "end_date",
            "in": "query",
            "required": false,
            "description": "List mode: end of date range (Y-m-d or Y-m-d H:i:s). Must fall within the current calendar month.",
            "schema": {
              "type": "string",
              "example": "2026-08-09"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "List mode: results per page (default: 50, max: 100)",
            "schema": {
              "type": "integer",
              "example": 50
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "List mode: page number (default: 1)",
            "schema": {
              "type": "integer",
              "example": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Statuses retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnexusStatusQueryResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters (bad IDs, invalid status, or date range outside the current calendar month)"
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/connexus/billing/query": {
      "post": {
        "tags": [
          "Connexus"
        ],
        "summary": "Billing Report (by rateCode)",
        "description": "Report billed SMS transactions grouped by rateCode, so resellers who tag sends with a per-customer rateCode can reconcile monthly on-billing. Summary mode (default) returns one row per rateCode with message count, segment count and amounts; detail=1 returns individual transactions with pagination. Only SMS deductions are included (never top-ups or adjustments). Billing detail is recorded from August 2026 onwards. Supports Bearer token auth or userId/password.",
        "operationId": "connexusBillingQuery",
        "security": [
          {
            "bearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account email (not needed if using Bearer token)",
            "schema": {
              "type": "string",
              "example": "user@domain.co.nz"
            }
          },
          {
            "name": "password",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account password (not needed if using Bearer token)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "month",
            "in": "query",
            "required": false,
            "description": "Reporting month in Y-m format. Defaults to the current month. Ignored when start_date/end_date are supplied.",
            "schema": {
              "type": "string",
              "example": "2026-08"
            }
          },
          {
            "name": "start_date",
            "in": "query",
            "required": false,
            "description": "Range start (Y-m-d or Y-m-d H:i:s). Must be supplied together with end_date. Max range 366 days.",
            "schema": {
              "type": "string",
              "example": "2026-08-01"
            }
          },
          {
            "name": "end_date",
            "in": "query",
            "required": false,
            "description": "Range end (Y-m-d or Y-m-d H:i:s). Must be supplied together with start_date.",
            "schema": {
              "type": "string",
              "example": "2026-08-31"
            }
          },
          {
            "name": "rateCode",
            "in": "query",
            "required": false,
            "description": "Report only this rateCode. A base code also matches its -OTP/-APPT variants, so filtering on your customer number returns that customer's full spend. Use \"untagged\" to select messages sent without a rateCode.",
            "schema": {
              "type": "string",
              "example": "CUST-0042"
            }
          },
          {
            "name": "detail",
            "in": "query",
            "required": false,
            "description": "Set to 1 to return individual billed transactions (paginated) instead of the per-rateCode summary.",
            "schema": {
              "type": "integer",
              "enum": [
                0,
                1
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Detail mode: rows per page (default 100, max 500)",
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 500
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Detail mode: page number (default 1)",
            "schema": {
              "type": "integer",
              "default": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Billing report",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnexusBillingQueryResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid parameters"
          },
          "401": {
            "description": "Unauthorized"
          }
        }
      }
    },
    "/connexus/sms/otp": {
      "post": {
        "tags": [
          "Connexus"
        ],
        "summary": "Send OTP/2FA Code",
        "description": "Send a one-time verification code for 2FA authentication. Code is auto-generated if not provided.",
        "operationId": "connexusSendOtp",
        "parameters": [
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account email (not needed if using Bearer token)",
            "schema": {
              "type": "string",
              "example": "user@domain.co.nz"
            }
          },
          {
            "name": "password",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account password (not needed if using Bearer token)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "description": "Recipient phone number",
            "schema": {
              "type": "string",
              "example": "6421234567"
            }
          },
          {
            "name": "msgCompany",
            "in": "query",
            "required": true,
            "description": "Company/app name shown in message",
            "schema": {
              "type": "string",
              "example": "MyApp"
            }
          },
          {
            "name": "msgCode",
            "in": "query",
            "required": false,
            "description": "Custom 4-8 digit code (auto-generated if not provided)",
            "schema": {
              "type": "string",
              "example": "426817"
            }
          },
          {
            "name": "msgComment",
            "in": "query",
            "required": false,
            "description": "Additional text appended to message",
            "schema": {
              "type": "string",
              "example": "Valid for 5 minutes."
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Sender ID (default: 2190)",
            "schema": {
              "type": "string",
              "example": "2190"
            }
          },
          {
            "name": "rateCode",
            "in": "query",
            "required": false,
            "description": "Billing tag (e.g. your own customer number). Reported in /connexus/billing/query as \"{rateCode}-OTP\"; sends without one report under the fixed code OTP.",
            "schema": {
              "type": "string",
              "example": "CUST-0042"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OTP sent successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnexusOtpResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing required parameters"
          },
          "401": {
            "description": "Invalid credentials"
          },
          "403": {
            "description": "Insufficient funds"
          }
        }
      }
    },
    "/connexus/sms/appointment": {
      "post": {
        "tags": [
          "Connexus"
        ],
        "summary": "Send Appointment Reminder",
        "description": "Send a templated appointment reminder. The message is built from the supplied fields (greeting, company, date/time, confirm prompt, callback number, address) and charged at the template rate of $0.08 per segment for NZ/AU destinations (or your account rate if lower; international destinations use standard international rates). Supports Bearer token auth or userId/password.",
        "operationId": "connexusSendAppointment",
        "security": [
          {
            "bearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account email (not needed if using Bearer token)",
            "schema": {
              "type": "string",
              "example": "user@domain.co.nz"
            }
          },
          {
            "name": "password",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account password (not needed if using Bearer token)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "description": "Recipient phone number",
            "schema": {
              "type": "string",
              "example": "6421234567"
            }
          },
          {
            "name": "msgCompany",
            "in": "query",
            "required": true,
            "description": "Business name shown in the reminder",
            "schema": {
              "type": "string",
              "example": "Lakeview Eyecare"
            }
          },
          {
            "name": "name",
            "in": "query",
            "required": false,
            "description": "Patient/customer first name for the greeting",
            "schema": {
              "type": "string",
              "example": "Sam"
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "Appointment date (free text, shown as supplied)",
            "schema": {
              "type": "string",
              "example": "Tuesday 12 Aug"
            }
          },
          {
            "name": "time",
            "in": "query",
            "required": false,
            "description": "Appointment time (free text, shown as supplied)",
            "schema": {
              "type": "string",
              "example": "2:30pm"
            }
          },
          {
            "name": "replyY",
            "in": "query",
            "required": false,
            "description": "Set to true to append a 'Reply Y to confirm' prompt",
            "schema": {
              "type": "boolean",
              "example": true
            }
          },
          {
            "name": "callUs",
            "in": "query",
            "required": false,
            "description": "Phone number to call to re-book (appended to the confirm prompt)",
            "schema": {
              "type": "string",
              "example": "03 123 4567"
            }
          },
          {
            "name": "address",
            "in": "query",
            "required": false,
            "description": "Practice/business address appended to the message",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Sender shortcode (must be authorised for your account; defaults to the shared template shortcode)",
            "schema": {
              "type": "string",
              "example": "552"
            }
          },
          {
            "name": "sandbox",
            "in": "query",
            "required": false,
            "description": "Set to true to test without sending or charging - the response echoes the built message and price",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "rateCode",
            "in": "query",
            "required": false,
            "description": "Billing tag (e.g. your own customer number). Reported in /connexus/billing/query as \"{rateCode}-APPT\"; sends without one report under the fixed code APPT.",
            "schema": {
              "type": "string",
              "example": "CUST-0042"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Reminder sent successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnexusSendResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing to or msgCompany"
          },
          "401": {
            "description": "Invalid credentials"
          },
          "403": {
            "description": "Insufficient funds or unauthorised sender"
          }
        }
      }
    },
    "/connexus/sms/balance": {
      "post": {
        "tags": [
          "Connexus"
        ],
        "summary": "Check Account Balance",
        "description": "Query your account balance before sending messages.",
        "operationId": "connexusCheckBalance",
        "parameters": [
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account email (not needed if using Bearer token)",
            "schema": {
              "type": "string",
              "example": "user@domain.co.nz"
            }
          },
          {
            "name": "password",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account password (not needed if using Bearer token)",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Balance retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnexusBalanceResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/connexus/mo/query": {
      "post": {
        "tags": [
          "Connexus"
        ],
        "summary": "Query Incoming Messages",
        "description": "Query received mobile-originated messages with filters and pagination.",
        "operationId": "connexusQueryMo",
        "parameters": [
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account email (not needed if using Bearer token)",
            "schema": {
              "type": "string",
              "example": "user@domain.co.nz"
            }
          },
          {
            "name": "password",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account password (not needed if using Bearer token)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Filter by sender number",
            "schema": {
              "type": "string",
              "example": "6421234567"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Filter by recipient (shortcode)",
            "schema": {
              "type": "string",
              "example": "551"
            }
          },
          {
            "name": "callback_status",
            "in": "query",
            "required": false,
            "description": "Filter by the HTTP status code your webhook returned (e.g. 200, 500). Useful for finding messages whose webhook delivery failed.",
            "schema": {
              "type": "string",
              "example": "200"
            }
          },
          {
            "name": "start_date",
            "in": "query",
            "required": false,
            "description": "Start of date range (Y-m-d or Y-m-d H:i:s)",
            "schema": {
              "type": "string",
              "example": "2025-01-01"
            }
          },
          {
            "name": "end_date",
            "in": "query",
            "required": false,
            "description": "End of date range (Y-m-d or Y-m-d H:i:s)",
            "schema": {
              "type": "string",
              "example": "2025-01-31"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Results per page (default: 100, max: 200)",
            "schema": {
              "type": "integer",
              "example": 100
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default: 1)",
            "schema": {
              "type": "integer",
              "example": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Messages retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnexusMoQueryResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/connexus/unsubscribes/query": {
      "post": {
        "tags": [
          "Connexus"
        ],
        "summary": "Query Blocked (Unsubscribed) Numbers",
        "description": "Fetch numbers that have opted out (STOP/END) of receiving messages from your account — the same list as Members Area > Blocked Numbers. Designed for delta syncing: pass `since` to fetch only entries blocked or resubscribed after that time, and use include=all to also receive resubscribed entries (active=false) so you can unblock them on your side. Supports Bearer token auth or userId/password.",
        "operationId": "connexusQueryUnsubscribes",
        "security": [
          {
            "bearerAuth": []
          },
          {}
        ],
        "parameters": [
          {
            "name": "userId",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account email (not needed if using Bearer token)",
            "schema": {
              "type": "string",
              "example": "user@domain.co.nz"
            }
          },
          {
            "name": "password",
            "in": "query",
            "required": false,
            "description": "Your WebSMS account password (not needed if using Bearer token)",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Only entries blocked or resubscribed at/after this time (Y-m-d or Y-m-d H:i:s). Omit for the full current list.",
            "schema": {
              "type": "string",
              "example": "2026-08-01"
            }
          },
          {
            "name": "include",
            "in": "query",
            "required": false,
            "description": "'active' (default) = currently blocked numbers only; 'all' = also include resubscribed entries with active=false",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "all"
              ],
              "example": "active"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Results per page (default: 100, max: 200)",
            "schema": {
              "type": "integer",
              "example": 100
            }
          },
          {
            "name": "page",
            "in": "query",
            "required": false,
            "description": "Page number (default: 1)",
            "schema": {
              "type": "integer",
              "example": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Blocked numbers retrieved successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnexusUnsubscribesResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid since date or include value"
          },
          "401": {
            "description": "Invalid credentials"
          }
        }
      }
    },
    "/salesforce/workflow.php": {
      "post": {
        "tags": [
          "Salesforce"
        ],
        "summary": "Send SMS from Salesforce",
        "description": "Endpoint for Salesforce Flow HTTP Callout or Apex HttpRequest to send SMS messages. Use this URL in your Salesforce Flow's HTTP Callout action.",
        "operationId": "sendSmsFromSalesforce",
        "parameters": [
          {
            "name": "cust_id",
            "in": "query",
            "required": true,
            "description": "Your WebSMS customer ID",
            "schema": {
              "type": "integer",
              "example": 123
            }
          },
          {
            "name": "shortcode_id",
            "in": "query",
            "required": true,
            "description": "Your shortcode ID",
            "schema": {
              "type": "integer",
              "example": 456
            }
          },
          {
            "name": "key",
            "in": "query",
            "required": true,
            "description": "API key from WebSMS integrations page",
            "schema": {
              "type": "string",
              "example": "abc123def456..."
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendSmsRequest"
              },
              "examples": {
                "basic": {
                  "summary": "Basic SMS",
                  "value": {
                    "to": "+6421234567",
                    "message": "Hello from Salesforce!"
                  }
                },
                "withMergeFields": {
                  "summary": "With Salesforce merge fields",
                  "value": {
                    "to": "{!Contact.Phone}",
                    "message": "Hi {!Contact.FirstName}, your appointment is confirmed for {!Event.StartDateTime}."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SMS sent successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendSmsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request - missing or invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed - invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient SMS balance",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Integration disabled or not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/hubspot/workflow.php": {
      "post": {
        "tags": [
          "HubSpot"
        ],
        "summary": "Send SMS from HubSpot Workflow",
        "description": "Endpoint for HubSpot Workflows to send SMS messages. Add this as a webhook action in your HubSpot workflow.",
        "operationId": "sendSmsFromHubspot",
        "parameters": [
          {
            "name": "cust_id",
            "in": "query",
            "required": true,
            "description": "Your WebSMS customer ID",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "shortcode_id",
            "in": "query",
            "required": true,
            "description": "Your shortcode ID",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "secret",
            "in": "query",
            "required": true,
            "description": "Webhook secret from WebSMS integrations page",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendSmsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SMS sent successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendSmsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request"
          },
          "401": {
            "description": "Authentication failed"
          }
        }
      }
    },
    "/zapier/webhook.php": {
      "post": {
        "tags": [
          "Zapier"
        ],
        "summary": "Send SMS from Zapier",
        "description": "Endpoint for Zapier actions to send SMS messages. Use this URL in your Zap's webhook action.",
        "operationId": "sendSmsFromZapier",
        "parameters": [
          {
            "name": "cust_id",
            "in": "query",
            "required": true,
            "description": "Your WebSMS customer ID",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "shortcode_id",
            "in": "query",
            "required": true,
            "description": "Your shortcode ID",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "key",
            "in": "query",
            "required": true,
            "description": "API key from WebSMS integrations page",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendSmsRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SMS sent successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendSmsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request"
          },
          "401": {
            "description": "Authentication failed"
          }
        }
      }
    },
    "/shopify/webhook.php": {
      "post": {
        "tags": [
          "Shopify"
        ],
        "summary": "Shopify Order Webhook",
        "description": "Receives order events from Shopify and sends SMS notifications. Configure this URL in Shopify Admin > Settings > Notifications > Webhooks.",
        "operationId": "shopifyWebhook",
        "parameters": [
          {
            "name": "cust_id",
            "in": "query",
            "required": true,
            "description": "Your WebSMS customer ID",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "shortcode_id",
            "in": "query",
            "required": true,
            "description": "Your shortcode ID",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "key",
            "in": "query",
            "required": true,
            "description": "API key from WebSMS integrations page",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Shopify order payload (sent automatically by Shopify)",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "Shopify order object - see Shopify documentation for full schema"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook processed successfully"
          },
          "401": {
            "description": "Authentication failed"
          }
        }
      }
    },
    "/zoho/send.php": {
      "post": {
        "tags": [
          "Zoho"
        ],
        "summary": "Send SMS from Zoho CRM",
        "description": "Send SMS messages from Zoho CRM workflows, custom functions, or buttons. Supports two authentication modes: API key (for shared shortcodes) or webhook secret (for owned shortcodes).",
        "operationId": "sendSmsFromZoho",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ZohoSendRequest"
              },
              "examples": {
                "sharedShortcode": {
                  "summary": "Using API Key (Shared Shortcode)",
                  "description": "For customers using the shared 2190 shortcode",
                  "value": {
                    "api_key": "zoho_abc123...",
                    "to": "+6421234567",
                    "message": "Hello from Zoho CRM!"
                  }
                },
                "ownedShortcode": {
                  "summary": "Using Webhook Secret (Owned Shortcode)",
                  "description": "For customers with their own dedicated shortcode",
                  "value": {
                    "shortcode_id": 123,
                    "secret": "your_webhook_secret",
                    "to": "+6421234567",
                    "message": "Hello from Zoho CRM!"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SMS sent successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ZohoSendResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request - missing or invalid parameters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "missingFields": {
                    "value": {
                      "success": false,
                      "error": "Missing required fields: to, message"
                    }
                  },
                  "invalidPhone": {
                    "value": {
                      "success": false,
                      "error": "Invalid phone number"
                    }
                  },
                  "missingAuth": {
                    "value": {
                      "success": false,
                      "error": "Authentication required: provide either api_key OR (shortcode_id + secret)"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "Authentication failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                },
                "examples": {
                  "invalidApiKey": {
                    "value": {
                      "success": false,
                      "error": "Invalid API key"
                    }
                  },
                  "integrationDisabled": {
                    "value": {
                      "success": false,
                      "error": "Zoho integration is disabled"
                    }
                  },
                  "invalidSecret": {
                    "value": {
                      "success": false,
                      "error": "Invalid webhook secret"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Resource not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/outlook/send.php": {
      "post": {
        "tags": [
          "Outlook"
        ],
        "summary": "Send SMS from the Outlook add-in",
        "description": "Called by the WebSMS Outlook add-in (taskpane) when the user clicks Send. Authenticate with the X-API-Key header containing the per-shortcode key from /members/integrations.php. Phone numbers default to NZ (+64) when no country code is supplied.",
        "operationId": "sendSmsFromOutlook",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "WebSMS API key for the Outlook integration on a specific shortcode."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendSmsRequest"
              },
              "examples": {
                "basic": {
                  "summary": "Send to one recipient",
                  "value": {
                    "to": "+6421234567",
                    "message": "Hello from Outlook"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "SMS sent (or queued for review/nighttime delivery)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendSmsResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "402": {
            "description": "Insufficient prepaid balance",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "SendSmsRequest": {
        "type": "object",
        "required": [
          "to",
          "message"
        ],
        "properties": {
          "to": {
            "type": "string",
            "description": "Recipient phone number. Can include country code (64) or start with 0 for NZ numbers.",
            "example": "+6421234567"
          },
          "message": {
            "type": "string",
            "description": "SMS message content. Max 1600 characters (10 segments).",
            "example": "Hello from WebSMS!",
            "maxLength": 1600
          },
          "phone": {
            "type": "string",
            "description": "Alternative field name for phone number (alias for 'to')",
            "example": "+6421234567"
          },
          "text": {
            "type": "string",
            "description": "Alternative field name for message (alias for 'message')",
            "example": "Hello from WebSMS!"
          }
        }
      },
      "SendSmsResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "description": "Whether the SMS was sent successfully",
            "example": true
          },
          "messageId": {
            "type": "string",
            "description": "Unique message ID for tracking",
            "example": "12345"
          },
          "to": {
            "type": "string",
            "description": "Normalized phone number the message was sent to",
            "example": "6421234567"
          },
          "segments": {
            "type": "integer",
            "description": "Number of SMS segments used",
            "example": 1
          },
          "status": {
            "type": "string",
            "description": "Message status",
            "example": "sent"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "example": false
          },
          "error": {
            "type": "string",
            "description": "Error message describing what went wrong",
            "example": "Invalid API key"
          }
        }
      },
      "ConnexusAuthRequest": {
        "type": "object",
        "required": [
          "userId",
          "password"
        ],
        "properties": {
          "userId": {
            "type": "string",
            "description": "Your WebSMS account email",
            "example": "user@domain.co.nz"
          },
          "password": {
            "type": "string",
            "description": "Your WebSMS account password",
            "example": "yourpassword"
          }
        }
      },
      "ConnexusSendRequest": {
        "type": "object",
        "required": [
          "userId",
          "password",
          "to",
          "body",
          "messageClass"
        ],
        "properties": {
          "userId": {
            "type": "string",
            "description": "Your WebSMS account email",
            "example": "user@domain.co.nz"
          },
          "password": {
            "type": "string",
            "description": "Your WebSMS account password",
            "example": "yourpassword"
          },
          "to": {
            "type": "string",
            "description": "Recipient phone number (international format)",
            "example": "6421234567"
          },
          "body": {
            "type": "string",
            "description": "SMS message content",
            "example": "Hello from WebSMS!"
          },
          "messageClass": {
            "type": "string",
            "enum": [
              "transactional",
              "marketing"
            ],
            "description": "Traffic classification for shortcode routing. Use `transactional` for OTP/2FA, confirmations, alerts, notifications and delivery updates; use `marketing` for promotional campaigns, offers and newsletters.",
            "example": "transactional"
          },
          "from": {
            "type": "string",
            "description": "Sender ID (shortcode)",
            "example": "2190"
          },
          "messageId": {
            "type": "string",
            "description": "Custom message identifier (max 36 chars). Echoed back as customerMessageId in delivery-status webhooks and /connexus/sms/status/query responses, as replyToCustomerMessageId in incoming-reply webhooks, and usable as the messageId lookup parameter on /connexus/sms/status/query. Not returned in the send response itself — that returns our message_id.",
            "example": "MSG123456"
          },
          "rateCode": {
            "type": "string",
            "description": "Message source identifier for reporting",
            "example": "CAMPAIGN1"
          },
          "fragmentationLimit": {
            "type": "integer",
            "description": "Maximum SMS segments, 1-11 (or 0 for no limit of your own). 11 segments is 1683 characters. A longer body is trimmed to fit and the response then carries truncated: true. Messages are sent as GSM-7; characters outside ASCII (emoji, non-Latin scripts) are replaced with '?' and macronised/accented vowels are folded, so this endpoint does not send UCS-2/Unicode.",
            "minimum": 0,
            "maximum": 11,
            "default": 11,
            "example": 11
          }
        }
      },
      "ConnexusSendResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "description": "Whether the message was accepted",
            "example": true
          },
          "status": {
            "type": "string",
            "description": "Message status",
            "example": "accepted"
          },
          "message_id": {
            "type": "string",
            "description": "Unique message ID for this send. This is the correlation key across the API: delivery-status webhooks reference it as messageId, incoming reply webhooks reference it as replyTo, /connexus/mo/query returns it as relatedMessageId, and /connexus/sms/status/query accepts it as the message_id lookup parameter.",
            "example": "1188236"
          },
          "to": {
            "type": "string",
            "description": "Normalized recipient phone number",
            "example": "6421234567"
          },
          "from": {
            "type": "string",
            "description": "Sender ID used",
            "example": "2190"
          },
          "parts": {
            "type": "integer",
            "description": "Number of SMS segments",
            "example": 1
          },
          "route": {
            "type": "string",
            "description": "SMSC route used for delivery (NZ numbers only)",
            "example": "smsc_2degrees"
          },
          "porting": {
            "type": "object",
            "description": "Carrier/porting information (NZ numbers with IPMS data only)",
            "properties": {
              "ported": {
                "type": "boolean",
                "description": "Whether the number has been ported",
                "example": false
              },
              "carrier": {
                "type": "string",
                "description": "Current carrier name",
                "example": "2degrees"
              },
              "source": {
                "type": "string",
                "description": "Data source (redis or ipms)",
                "example": "redis"
              }
            }
          },
          "served_by": {
            "type": "string",
            "description": "Which WebSMS host handled the request (e.g. web-19, atv-worker-1). Informational, for support; also returned as the X-Served-By header.",
            "example": "atv-worker-1"
          }
        }
      },
      "ConnexusBulkSendResponse": {
        "type": "object",
        "description": "Response when sending to multiple recipients",
        "properties": {
          "success": {
            "type": "boolean",
            "description": "Whether at least one message was sent successfully",
            "example": true
          },
          "messages": {
            "type": "array",
            "description": "Array of results for each recipient",
            "items": {
              "$ref": "#/components/schemas/ConnexusSendResponse"
            }
          },
          "summary": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer",
                "description": "Total number of recipients",
                "example": 2
              },
              "sent": {
                "type": "integer",
                "description": "Number of messages successfully sent",
                "example": 2
              },
              "failed": {
                "type": "integer",
                "description": "Number of messages that failed",
                "example": 0
              }
            }
          },
          "served_by": {
            "type": "string",
            "description": "Which WebSMS host handled the request (e.g. web-19, atv-worker-1). Informational, for support; also returned as the X-Served-By header.",
            "example": "atv-worker-1"
          }
        }
      },
      "TokenRequest": {
        "type": "object",
        "required": [
          "client_id",
          "client_secret"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "description": "API key public identifier (cid_xxx format)",
            "example": "cid_abc123..."
          },
          "client_secret": {
            "type": "string",
            "description": "API key secret (csk_xxx format)",
            "example": "csk_xyz789..."
          }
        }
      },
      "TokenResponse": {
        "type": "object",
        "properties": {
          "access_token": {
            "type": "string",
            "description": "Bearer token for API authentication",
            "example": "wst_abc123..."
          },
          "token_type": {
            "type": "string",
            "description": "Token type (always 'Bearer')",
            "example": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "description": "Token validity in seconds (86400 = 24 hours)",
            "example": 86400
          }
        }
      },
      "ConnexusWebhookRequest": {
        "type": "object",
        "required": [
          "userId",
          "password",
          "url"
        ],
        "properties": {
          "userId": {
            "type": "string",
            "description": "Your WebSMS account email",
            "example": "user@domain.co.nz"
          },
          "password": {
            "type": "string",
            "description": "Your WebSMS account password",
            "example": "yourpassword"
          },
          "url": {
            "type": "string",
            "description": "Webhook URL to receive callbacks",
            "example": "https://yoursite.com/webhook"
          }
        }
      },
      "ConnexusOtpRequest": {
        "type": "object",
        "required": [
          "userId",
          "password",
          "to",
          "msgCompany"
        ],
        "properties": {
          "userId": {
            "type": "string",
            "description": "Your WebSMS account email",
            "example": "user@domain.co.nz"
          },
          "password": {
            "type": "string",
            "description": "Your WebSMS account password",
            "example": "yourpassword"
          },
          "to": {
            "type": "string",
            "description": "Recipient phone number",
            "example": "6421234567"
          },
          "msgCompany": {
            "type": "string",
            "description": "Company/app name shown in message",
            "example": "MyApp"
          },
          "msgCode": {
            "type": "string",
            "description": "Custom 4-8 digit code (auto-generated if not provided)",
            "example": "426817"
          },
          "msgComment": {
            "type": "string",
            "description": "Additional text appended to message",
            "example": "Valid for 5 minutes."
          },
          "from": {
            "type": "string",
            "description": "Sender ID (default: 2190)",
            "example": "2190"
          }
        }
      },
      "ConnexusOtpResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "message_id": {
            "type": "string",
            "description": "Unique message ID",
            "example": "abc123def456"
          },
          "to": {
            "type": "string",
            "description": "Recipient phone number",
            "example": "6421234567"
          },
          "from": {
            "type": "string",
            "description": "Sender ID used",
            "example": "2190"
          },
          "code": {
            "type": "string",
            "description": "The OTP code that was sent (store this to verify later)",
            "example": "426817"
          },
          "parts": {
            "type": "integer",
            "description": "Number of SMS segments",
            "example": 1
          }
        }
      },
      "ConnexusBalanceResponse": {
        "type": "object",
        "properties": {
          "balance": {
            "type": "string",
            "description": "Current account balance",
            "example": "125.50"
          },
          "currency": {
            "type": "string",
            "description": "Currency code",
            "example": "NZD"
          }
        }
      },
      "NumberLookupResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "description": "Whether the lookup was successful",
            "example": true
          },
          "number": {
            "type": "string",
            "description": "Normalized phone number (international format)",
            "example": "6421234567"
          },
          "carrier": {
            "type": "string",
            "description": "Current carrier name",
            "example": "Spark"
          },
          "ported": {
            "type": "boolean",
            "description": "Whether the number has been ported from its original network",
            "example": false
          },
          "original_network": {
            "type": "string",
            "description": "Original network based on number prefix",
            "example": "Spark"
          },
          "current_network": {
            "type": "string",
            "description": "Current network servicing the number",
            "example": "Spark"
          },
          "network_code": {
            "type": "string",
            "description": "Network operator code",
            "example": "TCNZ"
          }
        }
      },
      "ConnexusMoQueryRequest": {
        "type": "object",
        "required": [
          "userId",
          "password"
        ],
        "properties": {
          "userId": {
            "type": "string",
            "description": "Your WebSMS account email",
            "example": "user@domain.co.nz"
          },
          "password": {
            "type": "string",
            "description": "Your WebSMS account password",
            "example": "yourpassword"
          },
          "from": {
            "type": "string",
            "description": "Filter by sender number",
            "example": "6421234567"
          },
          "to": {
            "type": "string",
            "description": "Filter by recipient (shortcode)",
            "example": "551"
          },
          "callback_status": {
            "type": "string",
            "description": "Filter by the HTTP status code your webhook returned (e.g. 200, 500). Useful for finding messages whose webhook delivery failed.",
            "example": "200"
          },
          "start_date": {
            "type": "string",
            "description": "Start of date range (Y-m-d or Y-m-d H:i:s)",
            "example": "2025-01-01"
          },
          "end_date": {
            "type": "string",
            "description": "End of date range (Y-m-d or Y-m-d H:i:s)",
            "example": "2025-01-31"
          },
          "limit": {
            "type": "integer",
            "description": "Results per page (default: 100, max: 200)",
            "example": 100
          },
          "page": {
            "type": "integer",
            "description": "Page number (default: 1)",
            "example": 1
          }
        }
      },
      "ConnexusMoQueryResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "messages": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "messageId": {
                  "type": "string",
                  "description": "Unique ID of this incoming message",
                  "example": "1234"
                },
                "from": {
                  "type": "string",
                  "example": "6421234567"
                },
                "to": {
                  "type": "string",
                  "example": "551"
                },
                "body": {
                  "type": "string",
                  "example": "Reply message text"
                },
                "receivedTime": {
                  "type": "string",
                  "example": "2025-01-15 14:30:00"
                },
                "timestamp": {
                  "type": "integer",
                  "example": 1736948400
                },
                "relatedMessageId": {
                  "type": "string",
                  "description": "Present when this message is a reply to a message you sent: the message_id returned by /connexus/sms/out for that original message (also delivered as replyTo in the incoming-SMS webhook). Omitted for keyword/user-initiated messages with no prior outbound message.",
                  "example": "1188236"
                },
                "callbackUrl": {
                  "type": "string",
                  "description": "Webhook URL this message was delivered to (only present if a webhook call was made)",
                  "example": "https://yoursite.com/webhook"
                },
                "callbackStatus": {
                  "type": "string",
                  "description": "HTTP status code returned by your webhook endpoint (only present if a webhook call was made)",
                  "example": "200"
                },
                "callbackResponse": {
                  "type": "string",
                  "description": "Response body returned by your webhook endpoint, truncated to 500 characters (only present if a webhook call was made)",
                  "example": "OK"
                }
              }
            }
          },
          "pagination": {
            "type": "object",
            "properties": {
              "page": {
                "type": "integer",
                "example": 1
              },
              "limit": {
                "type": "integer",
                "example": 100
              },
              "totalRecords": {
                "type": "integer",
                "example": 250
              },
              "totalPages": {
                "type": "integer",
                "example": 3
              },
              "hasNextPage": {
                "type": "boolean",
                "example": true
              },
              "hasPreviousPage": {
                "type": "boolean",
                "example": false
              }
            }
          }
        }
      },
      "ConnexusStatusQueryResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "messages": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "message_id": {
                  "type": "string",
                  "description": "The message ID returned by /connexus/sms/out",
                  "example": "1188236"
                },
                "to": {
                  "type": "string",
                  "example": "6421234567"
                },
                "from": {
                  "type": "string",
                  "example": "2190"
                },
                "status": {
                  "type": "string",
                  "description": "Delivery status. PENDING means no delivery report has been received yet. Same status strings as the delivery-status webhook.",
                  "enum": [
                    "DELIVRD",
                    "UNDELIV",
                    "ACCEPTD",
                    "QUEUED",
                    "FAILED",
                    "PENDING",
                    "BLOCKED",
                    "DELETED",
                    "UNKNOWN",
                    "NACKED",
                    "ENROUTE"
                  ],
                  "example": "DELIVRD"
                },
                "statusCode": {
                  "type": "integer",
                  "nullable": true,
                  "description": "Raw delivery report code (null if no report received yet)",
                  "example": 1
                },
                "sentTime": {
                  "type": "string",
                  "description": "When the message was submitted",
                  "example": "2026-08-09 10:15:22"
                },
                "statusTime": {
                  "type": "string",
                  "description": "When the delivery status was last updated (only present once a report has arrived)",
                  "example": "2026-08-09 10:15:31"
                },
                "customerMessageId": {
                  "type": "string",
                  "description": "Your own custom messageId supplied when sending (only present if you supplied one)",
                  "example": "PRAC-042-7731"
                },
                "rateCode": {
                  "type": "string",
                  "example": "CUST-0042",
                  "description": "The rateCode (billing tag) supplied when the message was sent. Only present when one was recorded. OTP/appointment sends show their suffixed tag (e.g. CUST-0042-OTP) or the fixed OTP/APPT codes."
                },
                "blockedReason": {
                  "type": "string",
                  "description": "Reason the message was blocked before submission (only present when status is BLOCKED)",
                  "example": "unsubscribed"
                }
              }
            }
          },
          "notFound": {
            "type": "array",
            "description": "message_id lookups only: requested IDs that were not found on your account",
            "items": {
              "type": "string"
            },
            "example": []
          },
          "pagination": {
            "type": "object",
            "description": "List mode only",
            "properties": {
              "page": {
                "type": "integer",
                "example": 1
              },
              "limit": {
                "type": "integer",
                "example": 50
              },
              "totalRecords": {
                "type": "integer",
                "example": 250
              },
              "totalPages": {
                "type": "integer",
                "example": 5
              },
              "hasNextPage": {
                "type": "boolean",
                "example": true
              },
              "hasPreviousPage": {
                "type": "boolean",
                "example": false
              }
            }
          }
        }
      },
      "ConnexusUnsubscribesResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "numbers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "number": {
                  "type": "string",
                  "description": "The opted-out mobile number",
                  "example": "6421234567"
                },
                "keyword": {
                  "type": "string",
                  "description": "The keyword that triggered the opt-out",
                  "example": "STOP"
                },
                "blockedAt": {
                  "type": "string",
                  "description": "When the number opted out (refreshed if they STOP again)",
                  "example": "2026-08-05 14:22:10"
                },
                "active": {
                  "type": "boolean",
                  "description": "true = currently blocked; false = has since resubscribed (only returned with include=all)",
                  "example": true
                },
                "shortcode": {
                  "type": "string",
                  "description": "Shortcode the STOP was sent to (only present when known)",
                  "example": "2190"
                },
                "resubscribedAt": {
                  "type": "string",
                  "description": "When the number resubscribed (only present if they did)",
                  "example": "2026-08-07 09:01:44"
                }
              }
            }
          },
          "pagination": {
            "type": "object",
            "properties": {
              "page": {
                "type": "integer",
                "example": 1
              },
              "limit": {
                "type": "integer",
                "example": 100
              },
              "totalRecords": {
                "type": "integer",
                "example": 42
              },
              "totalPages": {
                "type": "integer",
                "example": 1
              },
              "hasNextPage": {
                "type": "boolean",
                "example": false
              },
              "hasPreviousPage": {
                "type": "boolean",
                "example": false
              }
            }
          }
        }
      },
      "ZohoSendRequest": {
        "type": "object",
        "required": [
          "to",
          "message"
        ],
        "properties": {
          "api_key": {
            "type": "string",
            "description": "API key for shared shortcode authentication. Get this from WebSMS Integrations page.",
            "example": "zoho_abc123..."
          },
          "shortcode_id": {
            "type": "integer",
            "description": "Shortcode ID for owned shortcode authentication. Use with 'secret' parameter.",
            "example": 123
          },
          "secret": {
            "type": "string",
            "description": "Webhook secret for owned shortcode authentication. Use with 'shortcode_id' parameter.",
            "example": "your_webhook_secret"
          },
          "to": {
            "type": "string",
            "description": "Recipient phone number. Can include country code (64) or start with 0 for NZ numbers.",
            "example": "+6421234567"
          },
          "message": {
            "type": "string",
            "description": "SMS message content. Max 1600 characters (10 segments).",
            "example": "Hello from Zoho CRM!",
            "maxLength": 1600
          }
        }
      },
      "ZohoSendResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "description": "Whether the SMS was sent successfully",
            "example": true
          },
          "message_id": {
            "type": "integer",
            "description": "Unique message ID for tracking",
            "example": 12345
          },
          "segments": {
            "type": "integer",
            "description": "Number of SMS segments used",
            "example": 1
          },
          "from": {
            "type": "string",
            "description": "Sender shortcode used",
            "example": "2190"
          }
        }
      },
      "ConnexusBillingQueryResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "success"
          },
          "rateCodes": {
            "type": "array",
            "description": "Summary mode: one row per rateCode. Messages sent without a rateCode are grouped as \"untagged\". OTP and appointment sends are billed at their own rates and always report separately: as \"{rateCode}-OTP\" / \"{rateCode}-APPT\" when a rateCode was supplied, otherwise under the fixed codes OTP and APPT.",
            "items": {
              "type": "object",
              "properties": {
                "rateCode": {
                  "type": "string",
                  "example": "CUST-0042"
                },
                "sendType": {
                  "type": "string",
                  "enum": [
                    "sms",
                    "otp",
                    "appointment"
                  ],
                  "description": "Derived from the rateCode's -OTP/-APPT suffix (or the fixed OTP/APPT codes); sms otherwise."
                },
                "baseRateCode": {
                  "type": "string",
                  "example": "CUST-0042",
                  "description": "Only present on suffixed OTP/APPT rows: the rateCode with the -OTP/-APPT suffix removed, for grouping per customer."
                },
                "messages": {
                  "type": "integer",
                  "example": 1250
                },
                "segments": {
                  "type": "integer",
                  "example": 1311
                },
                "amount": {
                  "type": "number",
                  "description": "NZD exclusive of GST",
                  "example": 91.77
                },
                "gst": {
                  "type": "number",
                  "description": "GST (zero for non-NZ accounts)",
                  "example": 13.7655
                },
                "totalInclGst": {
                  "type": "number",
                  "example": 105.5355
                }
              }
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "messages": {
                "type": "integer"
              },
              "segments": {
                "type": "integer"
              },
              "amount": {
                "type": "number"
              },
              "gst": {
                "type": "number"
              },
              "totalInclGst": {
                "type": "number"
              }
            }
          },
          "transactions": {
            "type": "array",
            "description": "Detail mode only: individual billed transactions",
            "items": {
              "type": "object",
              "properties": {
                "date": {
                  "type": "string",
                  "example": "2026-08-14 10:15:22"
                },
                "description": {
                  "type": "string",
                  "example": "Connexus API SMS to 6421234567"
                },
                "rateCode": {
                  "type": "string",
                  "nullable": true
                },
                "segments": {
                  "type": "integer"
                },
                "amount": {
                  "type": "number"
                },
                "gst": {
                  "type": "number"
                },
                "totalInclGst": {
                  "type": "number"
                }
              }
            }
          },
          "filters": {
            "type": "object",
            "properties": {
              "rateCode": {
                "type": "string",
                "nullable": true
              },
              "start_date": {
                "type": "string"
              },
              "end_date": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Bearer token from /connexus/auth/token endpoint. Valid for 24 hours."
      },
      "apiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "key",
        "description": "API key passed as query parameter"
      },
      "basicAuth": {
        "type": "http",
        "scheme": "basic",
        "description": "Username (email) and password authentication"
      }
    }
  }
}