{
    "openapi": "3.1.0",
    "info": {
        "title": "Waesta OS API",
        "version": "1.0.0",
        "description": "Bulk SMS, support tickets (with webhooks), systems health and licence, and the website inquiry form.\n\nStart with docs/API.md: keys, errors, rate limits, retries and quick starts.\n\nEvery reply has `ok`. Errors are `{ \"ok\": false, \"error\": \"what to fix\" }`."
    },
    "servers": [
        {
            "url": "{baseUrl}",
            "variables": {
                "baseUrl": {
                    "default": "https://waestaos.waestaltd.com",
                    "description": "Your Waesta OS address, e.g. https://os.waestaltd.com"
                }
            }
        }
    ],
    "tags": [
        {
            "name": "SMS",
            "description": "Send SMS from a customer's prepaid credits"
        },
        {
            "name": "Support",
            "description": "Raise and follow support tickets"
        },
        {
            "name": "Systems",
            "description": "Report health and version, check the subscription, learn about upgrades"
        },
        {
            "name": "Website inquiry",
            "description": "The contact form on waestaltd.com"
        }
    ],
    "paths": {
        "/api/v1/sms/send": {
            "post": {
                "tags": [
                    "SMS"
                ],
                "operationId": "smsSend",
                "summary": "Send SMS",
                "security": [
                    {
                        "smsKey": []
                    }
                ],
                "description": "Reserves credits (parts x recipients), then queues the messages. Every message starts with the customer's sender name. Invalid or repeated numbers are refused and not charged.",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "pattern": "^[A-Za-z0-9._\\-:]{1,80}$"
                        },
                        "description": "Your unique id for this send. A retry with the same key returns the first result and is not charged again."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/SmsSendRequest"
                            },
                            "example": {
                                "to": [
                                    "0772345678",
                                    "+256701234567"
                                ],
                                "message": "Your code is 4821"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Accepted",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SmsSendResponse"
                                },
                                "example": {
                                    "ok": true,
                                    "batch_id": 12,
                                    "accepted": 2,
                                    "rejected": [],
                                    "credits_used": 2,
                                    "credits_used_by_wallet": {
                                        "uganda": 2,
                                        "international": 0
                                    },
                                    "parts_per_message": 1,
                                    "balance": 998,
                                    "balance_international": 40,
                                    "replayed": false
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "402": {
                        "description": "Not enough credits. Nothing is sent or charged.",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/InsufficientCredits"
                                },
                                "example": {
                                    "ok": false,
                                    "error": "Not enough SMS credits: this needs 5 and the balance is 3.",
                                    "balance": 3,
                                    "balance_international": 40
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Fix the request: no valid number, empty or too long message, more than 1,000 numbers, suspended account",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests (120 per minute per key) or too many wrong keys",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "SMS sending is temporarily unavailable; retry with the same Idempotency-Key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/sms/balance": {
            "get": {
                "tags": [
                    "SMS"
                ],
                "operationId": "smsBalance",
                "summary": "Credits, sender name and how long they should last",
                "security": [
                    {
                        "smsKey": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Balance",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SmsBalance"
                                },
                                "example": {
                                    "ok": true,
                                    "balance": 998,
                                    "sender_name": "Benofy",
                                    "status": "active",
                                    "average_daily_use": 120.5,
                                    "estimated_days_left": 8.3,
                                    "balance_international": 40,
                                    "estimated_days_left_international": null
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests (120 per minute per key) or too many wrong keys",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/sms/batches/{batch_id}": {
            "get": {
                "tags": [
                    "SMS"
                ],
                "operationId": "smsBatch",
                "summary": "Delivery status of one send",
                "security": [
                    {
                        "smsKey": []
                    }
                ],
                "parameters": [
                    {
                        "name": "batch_id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        },
                        "description": "The batch_id returned by Send"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Batch and each number's status",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SmsBatch"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No such batch for this key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests (120 per minute per key) or too many wrong keys",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/support/tickets": {
            "post": {
                "tags": [
                    "Support"
                ],
                "operationId": "ticketCreate",
                "summary": "Raise a ticket",
                "security": [
                    {
                        "careKey": []
                    }
                ],
                "description": "The same `external_ref` returns the existing ticket (`replayed: true`) instead of creating a new one, so retries are safe.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/TicketCreate"
                            },
                            "example": {
                                "subject": "Report will not export",
                                "message": "Clicking Export does nothing.",
                                "kind": "issue",
                                "priority": "high",
                                "external_ref": "BEN-4471",
                                "reporter": {
                                    "name": "Grace Auma",
                                    "email": "grace@school.example",
                                    "phone": "+256772000111"
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Ticket created",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/TicketEnvelope"
                                }
                            }
                        }
                    },
                    "200": {
                        "description": "Already existed for this external_ref (replayed)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/TicketEnvelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Missing subject or message, bad email, phone or external_ref",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests (120 per minute per key) or too many wrong keys",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            },
            "get": {
                "tags": [
                    "Support"
                ],
                "operationId": "ticketList",
                "summary": "List this system's tickets",
                "security": [
                    {
                        "careKey": []
                    }
                ],
                "parameters": [
                    {
                        "name": "status",
                        "in": "query",
                        "schema": {
                            "type": "string",
                            "enum": [
                                "open",
                                "in_progress",
                                "awaiting_client",
                                "resolved",
                                "closed"
                            ]
                        }
                    },
                    {
                        "name": "external_ref",
                        "in": "query",
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100,
                            "default": 25
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Tickets, newest first",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "ok": {
                                            "type": "boolean"
                                        },
                                        "tickets": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#/components/schemas/Ticket"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests (120 per minute per key) or too many wrong keys",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/support/tickets/{ticket_no}": {
            "get": {
                "tags": [
                    "Support"
                ],
                "operationId": "ticketGet",
                "summary": "Status, resolution and replies",
                "security": [
                    {
                        "careKey": []
                    }
                ],
                "parameters": [
                    {
                        "name": "ticket_no",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The ticket number returned when it was raised, e.g. TICKET-2026-00012",
                        "example": "TICKET-2026-00012"
                    }
                ],
                "description": "Internal notes, the internal root cause and staff names are never included.",
                "responses": {
                    "200": {
                        "description": "The ticket with its replies",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/TicketEnvelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No such ticket for this key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests (120 per minute per key) or too many wrong keys",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/support/tickets/{ticket_no}/replies": {
            "post": {
                "tags": [
                    "Support"
                ],
                "operationId": "ticketReply",
                "summary": "Add a message from the customer",
                "security": [
                    {
                        "careKey": []
                    }
                ],
                "parameters": [
                    {
                        "name": "ticket_no",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The ticket number returned when it was raised, e.g. TICKET-2026-00012",
                        "example": "TICKET-2026-00012"
                    }
                ],
                "description": "A reply on a resolved or closed ticket re-opens it.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "message"
                                ],
                                "properties": {
                                    "message": {
                                        "type": "string",
                                        "description": "The customer's message",
                                        "maxLength": 10000
                                    }
                                }
                            },
                            "example": {
                                "message": "It is still failing"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated ticket",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/TicketEnvelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No such ticket for this key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "message is required",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests (120 per minute per key) or too many wrong keys",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/support/tickets/{ticket_no}/feedback": {
            "post": {
                "tags": [
                    "Support"
                ],
                "operationId": "ticketFeedback",
                "summary": "Pass on the customer's rating",
                "security": [
                    {
                        "careKey": []
                    }
                ],
                "parameters": [
                    {
                        "name": "ticket_no",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "The ticket number returned when it was raised, e.g. TICKET-2026-00012",
                        "example": "TICKET-2026-00012"
                    }
                ],
                "description": "Once per resolution, and only after the ticket is resolved. A rating of 1 or 2 re-opens it and alerts the team; 3 to 5 closes it.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/Feedback"
                            },
                            "example": {
                                "rating": 5,
                                "comment": "Fast and polite"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "The updated ticket",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/TicketEnvelope"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No such ticket for this key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Not resolved yet, already rated, or rating outside 1 to 5",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests (120 per minute per key) or too many wrong keys",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/inquiry/config": {
            "get": {
                "tags": [
                    "Website inquiry"
                ],
                "operationId": "inquiryConfig",
                "summary": "What the contact form needs to draw itself",
                "security": [],
                "responses": {
                    "200": {
                        "description": "Turnstile site key, service list and company name",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "ok": {
                                            "type": "boolean"
                                        },
                                        "siteKey": {
                                            "type": "string"
                                        },
                                        "services": {
                                            "type": "array",
                                            "items": {
                                                "type": "string"
                                            }
                                        },
                                        "company": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Origin not allowed",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/inquiry": {
            "post": {
                "tags": [
                    "Website inquiry"
                ],
                "operationId": "inquirySubmit",
                "summary": "Send an inquiry from the website",
                "security": [],
                "description": "Only from the allowed website origins. Protected by Cloudflare Turnstile, a minimum fill time, a hidden trap field and rate limits. Creates an inquiry, a prospect client and a lead.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/x-www-form-urlencoded": {
                            "schema": {
                                "$ref": "#/components/schemas/InquiryRequest"
                            }
                        },
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/InquiryRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Received",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "ok": {
                                            "type": "boolean"
                                        },
                                        "reference": {
                                            "oneOf": [
                                                {
                                                    "type": "string"
                                                },
                                                {
                                                    "type": "null"
                                                }
                                            ]
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "reference": "INQ-00012"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Origin not allowed",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Validation, security check or rate limit message for the visitor",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "The form is not switched on yet",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/sms/bundles": {
            "get": {
                "tags": [
                    "SMS"
                ],
                "operationId": "smsBundles",
                "summary": "Credit packs you can buy, with prices",
                "security": [
                    {
                        "smsKey": []
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Active bundles",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object"
                                },
                                "example": {
                                    "ok": true,
                                    "bundles": [
                                        {
                                            "id": 3,
                                            "name": "Bundle 1000",
                                            "wallet": "local",
                                            "wallet_name": "Uganda",
                                            "credits": 1000,
                                            "price": "50000.00",
                                            "price_per_credit": 50,
                                            "currency": "UGX",
                                            "mobile_money_providers": {
                                                "mtn_momo": "MTN Mobile Money",
                                                "airtel": "Airtel Money (Uganda)"
                                            }
                                        }
                                    ]
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/sms/purchases": {
            "post": {
                "tags": [
                    "SMS"
                ],
                "operationId": "smsBuyCredits",
                "summary": "Buy credits: optionally prompt the payer's phone for mobile money",
                "security": [
                    {
                        "smsKey": []
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "bundle_id"
                                ],
                                "properties": {
                                    "bundle_id": {
                                        "type": "integer"
                                    },
                                    "provider": {
                                        "type": "string",
                                        "description": "mtn_momo, airtel, mpesa or airtel_ke. Send together with phone."
                                    },
                                    "phone": {
                                        "type": "string",
                                        "description": "The payer's mobile money number"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Purchase started; credits arrive when paid",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "ok": {
                                            "type": "boolean"
                                        },
                                        "purchase_id": {
                                            "type": "integer"
                                        },
                                        "status": {
                                            "type": "string",
                                            "enum": [
                                                "pending",
                                                "credited",
                                                "reversed",
                                                "cancelled"
                                            ]
                                        },
                                        "credits": {
                                            "type": "integer"
                                        },
                                        "amount": {
                                            "type": "string"
                                        },
                                        "currency": {
                                            "type": "string"
                                        },
                                        "pay_url": {
                                            "type": "string"
                                        },
                                        "balance": {
                                            "type": "integer"
                                        },
                                        "payment": {
                                            "type": [
                                                "object",
                                                "null"
                                            ]
                                        },
                                        "balance_international": {
                                            "type": "integer",
                                            "description": "International credits left"
                                        },
                                        "wallet": {
                                            "type": "string",
                                            "enum": [
                                                "local",
                                                "international"
                                            ]
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "purchase_id": 41,
                                    "status": "pending",
                                    "bundle": "Bundle 1000",
                                    "credits": 1000,
                                    "amount": "59000.00",
                                    "currency": "UGX",
                                    "invoice_no": "INV-000123",
                                    "pay_url": "https://YOUR-SYSTEM/i/ab12...",
                                    "balance": 12,
                                    "payment": {
                                        "request_id": 9,
                                        "status": "pending",
                                        "provider": "mtn_momo",
                                        "phone_last4": "5678"
                                    },
                                    "balance_international": 0,
                                    "wallet": "local"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Fix the request (unknown bundle, bad phone, suspended account)",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Too many purchases started",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/sms/purchases/{id}": {
            "get": {
                "tags": [
                    "SMS"
                ],
                "operationId": "smsPurchaseStatus",
                "summary": "Poll a purchase until status is credited",
                "security": [
                    {
                        "smsKey": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "integer"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Purchase and latest payment",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "ok": {
                                            "type": "boolean"
                                        },
                                        "purchase_id": {
                                            "type": "integer"
                                        },
                                        "status": {
                                            "type": "string",
                                            "enum": [
                                                "pending",
                                                "credited",
                                                "reversed",
                                                "cancelled"
                                            ]
                                        },
                                        "credits": {
                                            "type": "integer"
                                        },
                                        "amount": {
                                            "type": "string"
                                        },
                                        "currency": {
                                            "type": "string"
                                        },
                                        "pay_url": {
                                            "type": "string"
                                        },
                                        "balance": {
                                            "type": "integer"
                                        },
                                        "payment": {
                                            "type": [
                                                "object",
                                                "null"
                                            ]
                                        },
                                        "balance_international": {
                                            "type": "integer",
                                            "description": "International credits left"
                                        },
                                        "wallet": {
                                            "type": "string",
                                            "enum": [
                                                "local",
                                                "international"
                                            ]
                                        }
                                    }
                                },
                                "example": {
                                    "ok": true,
                                    "purchase_id": 41,
                                    "status": "credited",
                                    "bundle": "Bundle 1000",
                                    "credits": 1000,
                                    "amount": "59000.00",
                                    "currency": "UGX",
                                    "invoice_no": "INV-000123",
                                    "pay_url": "https://YOUR-SYSTEM/i/ab12...",
                                    "balance": 1012,
                                    "payment": {
                                        "request_id": 9,
                                        "status": "pending",
                                        "provider": "mtn_momo",
                                        "phone_last4": "5678"
                                    },
                                    "balance_international": 0,
                                    "wallet": "local"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No such purchase for this key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/systems/heartbeat": {
            "post": {
                "tags": [
                    "Systems"
                ],
                "operationId": "systemsHeartbeat",
                "summary": "Heartbeat: report health and version",
                "security": [
                    {
                        "careKey": []
                    }
                ],
                "description": "Send every 1 to 5 minutes from a scheduled job on your server. The first call adds the system to Waesta OS Operations. `status: down`, or no heartbeat for longer than the silence limit (default 30 minutes), opens an incident and alerts the duty contact; the next `ok` closes it. The reply carries the licence and the newest version.",
                "requestBody": {
                    "required": false,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/HeartbeatRequest"
                            },
                            "example": {
                                "status": "ok",
                                "version": "2.4.1",
                                "environment": "production",
                                "errors_24h": 3,
                                "active_users": 412,
                                "metrics": {
                                    "queue_depth": 12,
                                    "backup_ok": true
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Recorded",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/HeartbeatResponse"
                                },
                                "example": {
                                    "ok": true,
                                    "server_time": "2026-10-03T08:15:02+00:00",
                                    "latest_version": "2.5.0",
                                    "update_available": true,
                                    "licence": {
                                        "linked": true,
                                        "entitled": true,
                                        "status": "active",
                                        "subscription": "SUB-00042",
                                        "plan": "Benofy school licence",
                                        "plan_code": "pro",
                                        "limits": {
                                            "members": 500,
                                            "sms_per_month": 1000,
                                            "white_label": true
                                        },
                                        "renewal_date": "2026-12-01",
                                        "days_left": 59,
                                        "note": null,
                                        "exempt": false,
                                        "account_kind": "client",
                                        "pending_change": null
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "A field is wrong: status not ok, degraded or down; a count that is not a whole number; bad metrics",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests (120 per minute per key) or too many wrong keys",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/subscription": {
            "get": {
                "tags": [
                    "Systems"
                ],
                "operationId": "systemsSubscription",
                "summary": "Licence: is this system's subscription current?",
                "security": [
                    {
                        "careKey": []
                    }
                ],
                "description": "Answers from the subscription this key is linked to (Customer care, Systems, Link licence). `entitled` is true for trial, active and pending_renewal; false for suspended, expired and cancelled; null when no subscription is linked. Cache the last good answer and keep running if this service is unreachable.",
                "responses": {
                    "200": {
                        "description": "Licence status",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Licence"
                                },
                                "example": {
                                    "ok": true,
                                    "linked": true,
                                    "entitled": true,
                                    "status": "active",
                                    "subscription": "SUB-00042",
                                    "plan": "Benofy school licence",
                                    "plan_code": "pro",
                                    "limits": {
                                        "members": 500,
                                        "sms_per_month": 1000,
                                        "white_label": true
                                    },
                                    "renewal_date": "2026-12-01",
                                    "days_left": 59,
                                    "note": null,
                                    "exempt": false,
                                    "account_kind": "client",
                                    "pending_change": null
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests (120 per minute per key) or too many wrong keys",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/sms/rates": {
            "get": {
                "tags": [
                    "SMS"
                ],
                "operationId": "smsRates",
                "summary": "Countries we send to and the credits each message part costs",
                "security": [
                    {
                        "smsKey": []
                    }
                ],
                "description": "Uganda is 1 credit per part; Kenya and Tanzania cost more. Read this rather than hard-coding the numbers. Also shows your account's default country, used for numbers written without a country code.",
                "responses": {
                    "200": {
                        "description": "Rates",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/SmsRates"
                                },
                                "example": {
                                    "ok": true,
                                    "default_country": "UG",
                                    "countries": {
                                        "UG": {
                                            "name": "Uganda",
                                            "credits_per_part": 1,
                                            "wallet": "local"
                                        },
                                        "KE": {
                                            "name": "Kenya",
                                            "credits_per_part": 1,
                                            "wallet": "international"
                                        },
                                        "TZ": {
                                            "name": "Tanzania",
                                            "credits_per_part": 1,
                                            "wallet": "international"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests (120 per minute per key) or too many wrong keys",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/plans": {
            "get": {
                "tags": [
                    "Systems"
                ],
                "operationId": "systemsPlans",
                "summary": "Plans this client can move to",
                "security": [
                    {
                        "careKey": []
                    }
                ],
                "description": "The other plans of the same product with their price for this client, limits, and whether each is an upgrade or a downgrade. Needs the key to be licensed under a subscription.",
                "responses": {
                    "200": {
                        "description": "Plans",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Plans"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Too many requests",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/api/v1/subscription/plan": {
            "post": {
                "tags": [
                    "Systems"
                ],
                "operationId": "systemsChangePlan",
                "summary": "Move the client to another plan",
                "security": [
                    {
                        "careKey": []
                    }
                ],
                "description": "Self-service plan change (can be switched off by Waesta). An upgrade starts now and invoices the price difference for the rest of the paid period; a downgrade starts at the next renewal. `preview: true` shows the effect without changing anything.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/PlanChangeRequest"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Applied, scheduled or previewed",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/PlanChange"
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Missing, wrong or revoked key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "Plan changes are made by Waesta for this account",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "No subscription is linked to this key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Unknown plan_code, already on that plan, subscription not live, or a price is needed",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "More than 10 plan changes an hour from this key",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/Error"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "webhooks": {
        "ticketEvent": {
            "post": {
                "summary": "Ticket event (sent to your webhook address)",
                "operationId": "ticketWebhook",
                "description": "Sent when we reply, resolve, close or re-open a ticket raised through the API, or when a rating is recorded.\n\nVerify `X-Waesta-Signature` (`sha256=` + HMAC-SHA256 of the raw body with your webhook secret) before trusting a call. Answer with any 2xx. Retries after 1 min, 5 min, 30 min, 2 h and 12 h.",
                "parameters": [
                    {
                        "name": "X-Waesta-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "example": "sha256=9f2c..."
                        }
                    },
                    {
                        "name": "X-Waesta-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-Waesta-Delivery",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "description": "Unique id: ignore repeats"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/WebhookEvent"
                            }
                        }
                    }
                },
                "responses": {
                    "2XX": {
                        "description": "Received"
                    }
                }
            }
        },
        "smsCreditsEvent": {
            "post": {
                "summary": "SMS credits event (sent to your callback address)",
                "operationId": "smsCreditsWebhook",
                "description": "Sent when credits are added (`credits.added`), taken back (`credits.reversed`) or running low (`balance.low`), and for the test button (`ping`). Set the address under SMS credits > Connect your system.\n\nVerify `X-Waesta-Signature` (`sha256=` + HMAC-SHA256 of the raw body with your signing secret) before trusting a call. Answer with any 2xx. Retries after 1 min, 5 min, 30 min, 2 h and 12 h.",
                "parameters": [
                    {
                        "name": "X-Waesta-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "example": "sha256=9f2c..."
                        }
                    },
                    {
                        "name": "X-Waesta-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-Waesta-Delivery",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "description": "Unique id: ignore repeats"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "event": {
                                        "type": "string",
                                        "enum": [
                                            "credits.added",
                                            "credits.reversed",
                                            "balance.low",
                                            "ping"
                                        ]
                                    },
                                    "sent_at": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "balance": {
                                        "type": "integer"
                                    },
                                    "purchase": {
                                        "type": "object",
                                        "properties": {
                                            "id": {
                                                "type": "integer"
                                            },
                                            "status": {
                                                "type": "string"
                                            },
                                            "bundle": {
                                                "type": "string"
                                            },
                                            "credits": {
                                                "type": "integer"
                                            },
                                            "amount": {
                                                "type": "string"
                                            },
                                            "currency": {
                                                "type": "string"
                                            },
                                            "wallet": {
                                                "type": "string",
                                                "enum": [
                                                    "local",
                                                    "international"
                                                ]
                                            }
                                        }
                                    },
                                    "balance_international": {
                                        "type": "integer",
                                        "description": "International credits left"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "2XX": {
                        "description": "Received"
                    }
                }
            }
        },
        "licenceChanged": {
            "post": {
                "summary": "Licence event (sent to your webhook address)",
                "operationId": "licenceWebhook",
                "description": "Sent when the subscription is suspended, reactivated, expires, is cancelled, or its plan changes or is scheduled to change, or the key is linked or unlinked. `event` is `licence.changed`; `reason` says why; `licence` is the same object as `GET /subscription`, so replace your cached licence with it. Same signature and retry rules as ticket events; keep checking `GET /subscription` as well.",
                "parameters": [
                    {
                        "name": "X-Waesta-Signature",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "example": "sha256=9f2c..."
                        }
                    },
                    {
                        "name": "X-Waesta-Event",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "X-Waesta-Delivery",
                        "in": "header",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "description": "Unique id: ignore repeats"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "event": {
                                        "const": "licence.changed"
                                    },
                                    "sent_at": {
                                        "type": "string",
                                        "format": "date-time"
                                    },
                                    "reason": {
                                        "type": "string",
                                        "enum": [
                                            "suspended",
                                            "suspended_unpaid",
                                            "reactivated",
                                            "reactivated_paid",
                                            "expired",
                                            "cancelled",
                                            "plan_changed",
                                            "plan_change_scheduled",
                                            "plan_change_cancelled",
                                            "linked",
                                            "unlinked",
                                            "status_changed"
                                        ]
                                    },
                                    "licence": {
                                        "$ref": "#/components/schemas/Licence"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "2XX": {
                        "description": "Received"
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "smsKey": {
                "type": "http",
                "scheme": "bearer",
                "description": "SMS key `wsk_...` (or send it as the X-Api-Key header). Created for a customer's SMS account; shown once."
            },
            "careKey": {
                "type": "http",
                "scheme": "bearer",
                "description": "Support key `wcp_...` (or X-Api-Key). Created on the system's page in Customer care; shown once."
            }
        },
        "schemas": {
            "Error": {
                "type": "object",
                "required": [
                    "ok",
                    "error"
                ],
                "properties": {
                    "ok": {
                        "const": false
                    },
                    "error": {
                        "type": "string",
                        "description": "What to fix"
                    }
                }
            },
            "InsufficientCredits": {
                "type": "object",
                "properties": {
                    "ok": {
                        "const": false
                    },
                    "error": {
                        "type": "string",
                        "description": "Names the wallet that ran short when it is the International one"
                    },
                    "balance": {
                        "type": "integer",
                        "description": "Uganda credits left"
                    },
                    "balance_international": {
                        "type": "integer",
                        "description": "International credits left"
                    }
                }
            },
            "SmsSendRequest": {
                "type": "object",
                "required": [
                    "to",
                    "message"
                ],
                "properties": {
                    "to": {
                        "description": "One number or a list of up to 1,000. Accepts 0772345678, +256772345678 and 256772345678.",
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "array",
                                "maxItems": 1000,
                                "items": {
                                    "type": "string"
                                }
                            }
                        ]
                    },
                    "message": {
                        "type": "string",
                        "description": "The text. The customer's sender name is added to the start for you; the total may be at most 640 characters.",
                        "maxLength": 640
                    },
                    "country": {
                        "type": "string",
                        "enum": [
                            "UG",
                            "KE",
                            "TZ"
                        ],
                        "description": "Country to read numbers written without a country code in, for this call only. Defaults to the account's default country. Numbers with their country code are never changed."
                    }
                }
            },
            "SmsSendResponse": {
                "type": "object",
                "properties": {
                    "ok": {
                        "type": "boolean"
                    },
                    "batch_id": {
                        "type": "integer",
                        "description": "Use it to check delivery"
                    },
                    "accepted": {
                        "type": "integer",
                        "description": "Queued for sending"
                    },
                    "rejected": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "number": {
                                    "type": "string"
                                },
                                "reason": {
                                    "type": "string",
                                    "description": "Invalid phone number, Duplicate in this request, or We cannot send to this country"
                                }
                            }
                        },
                        "description": "Not sent and not charged"
                    },
                    "credits_used": {
                        "type": "integer",
                        "description": "Sum over accepted recipients of parts x credits per part of the destination country, from both wallets"
                    },
                    "parts_per_message": {
                        "type": "integer"
                    },
                    "balance": {
                        "type": "integer",
                        "description": "Uganda credits left"
                    },
                    "replayed": {
                        "type": "boolean",
                        "description": "True when this repeated an earlier call with the same Idempotency-Key"
                    },
                    "by_country": {
                        "type": "object",
                        "description": "Messages and credits per destination country",
                        "additionalProperties": {
                            "type": "object",
                            "properties": {
                                "messages": {
                                    "type": "integer"
                                },
                                "credits": {
                                    "type": "integer"
                                }
                            }
                        }
                    },
                    "credits_used_by_wallet": {
                        "type": "object",
                        "description": "Credits used from each wallet",
                        "properties": {
                            "uganda": {
                                "type": "integer"
                            },
                            "international": {
                                "type": "integer"
                            }
                        }
                    },
                    "balance_international": {
                        "type": "integer",
                        "description": "International credits left (Kenya and Tanzania)"
                    }
                }
            },
            "SmsBalance": {
                "type": "object",
                "properties": {
                    "ok": {
                        "type": "boolean"
                    },
                    "balance": {
                        "type": "integer",
                        "description": "Uganda credits (for numbers in Uganda, priced in UGX)"
                    },
                    "sender_name": {
                        "type": "string"
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "active",
                            "suspended"
                        ]
                    },
                    "average_daily_use": {
                        "oneOf": [
                            {
                                "type": "number"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "estimated_days_left": {
                        "oneOf": [
                            {
                                "type": "number"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "default_country": {
                        "type": "string",
                        "enum": [
                            "UG",
                            "KE",
                            "TZ"
                        ],
                        "description": "Country assumed for numbers written without a country code"
                    },
                    "balance_international": {
                        "type": "integer",
                        "description": "International credits (for Kenya and Tanzania, priced in USD)"
                    },
                    "estimated_days_left_international": {
                        "oneOf": [
                            {
                                "type": "number"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    }
                }
            },
            "SmsBatch": {
                "type": "object",
                "properties": {
                    "ok": {
                        "type": "boolean"
                    },
                    "batch": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "integer"
                            },
                            "source": {
                                "type": "string"
                            },
                            "recipients": {
                                "type": "integer"
                            },
                            "accepted": {
                                "type": "integer"
                            },
                            "rejected": {
                                "type": "integer"
                            },
                            "units": {
                                "type": "integer",
                                "description": "Credits used"
                            },
                            "created_at": {
                                "type": "string"
                            }
                        }
                    },
                    "progress": {
                        "type": "object",
                        "properties": {
                            "queued": {
                                "type": "integer"
                            },
                            "sent": {
                                "type": "integer"
                            },
                            "delivered": {
                                "type": "integer"
                            },
                            "failed": {
                                "type": "integer"
                            }
                        }
                    },
                    "messages": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "number": {
                                    "type": "string"
                                },
                                "status": {
                                    "type": "string",
                                    "enum": [
                                        "queued",
                                        "sending",
                                        "sent",
                                        "delivered",
                                        "failed",
                                        "skipped",
                                        "unknown"
                                    ],
                                    "description": "failed and skipped are refunded"
                                },
                                "error": {
                                    "oneOf": [
                                        {
                                            "type": "string"
                                        },
                                        {
                                            "type": "null"
                                        }
                                    ]
                                },
                                "delivered_at": {
                                    "oneOf": [
                                        {
                                            "type": "string"
                                        },
                                        {
                                            "type": "null"
                                        }
                                    ]
                                }
                            }
                        }
                    }
                }
            },
            "Reporter": {
                "type": "object",
                "description": "The end user behind the ticket. With an email, replies, the resolution and the feedback request go to that person.",
                "properties": {
                    "name": {
                        "type": "string",
                        "maxLength": 120
                    },
                    "email": {
                        "type": "string",
                        "format": "email"
                    },
                    "phone": {
                        "type": "string"
                    }
                }
            },
            "TicketCreate": {
                "type": "object",
                "required": [
                    "subject",
                    "message"
                ],
                "properties": {
                    "subject": {
                        "type": "string",
                        "maxLength": 180
                    },
                    "message": {
                        "type": "string",
                        "description": "What happened, what was expected, what was seen",
                        "maxLength": 10000
                    },
                    "kind": {
                        "type": "string",
                        "enum": [
                            "issue",
                            "complaint",
                            "question",
                            "suggestion"
                        ],
                        "default": "issue",
                        "description": "A complaint is at least high priority"
                    },
                    "priority": {
                        "type": "string",
                        "enum": [
                            "low",
                            "normal",
                            "high",
                            "urgent"
                        ],
                        "default": "normal",
                        "description": "First-reply targets: urgent 2h, high 8h, normal 24h, low 72h"
                    },
                    "category": {
                        "type": "string",
                        "enum": [
                            "technical",
                            "general",
                            "billing",
                            "project",
                            "hosting",
                            "domain"
                        ],
                        "default": "technical"
                    },
                    "external_ref": {
                        "type": "string",
                        "description": "Your own id for the problem; makes a retry safe",
                        "maxLength": 80,
                        "pattern": "^[A-Za-z0-9._\\-:#/]{1,80}$"
                    },
                    "reporter": {
                        "$ref": "#/components/schemas/Reporter"
                    }
                }
            },
            "Ticket": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer"
                    },
                    "ticket_no": {
                        "type": "string"
                    },
                    "external_ref": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "subject": {
                        "type": "string"
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "open",
                            "in_progress",
                            "awaiting_client",
                            "resolved",
                            "closed"
                        ]
                    },
                    "kind": {
                        "type": "string"
                    },
                    "priority": {
                        "type": "string"
                    },
                    "system": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "opened_at": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "first_response_at": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "resolved_at": {
                        "oneOf": [
                            {
                                "type": "string"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "first_reply_due": {
                        "type": "string",
                        "description": "UTC"
                    },
                    "resolution_type": {
                        "oneOf": [
                            {
                                "type": "string",
                                "enum": [
                                    "fixed",
                                    "workaround",
                                    "advice",
                                    "upgrade",
                                    "no_fault",
                                    "duplicate",
                                    "wont_fix"
                                ]
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "resolution": {
                        "oneOf": [
                            {
                                "type": "string",
                                "description": "What we did (shown to the customer)"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "feedback_link": {
                        "oneOf": [
                            {
                                "type": "string",
                                "description": "Page where the customer can rate the help"
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "feedback": {
                        "oneOf": [
                            {
                                "type": "object",
                                "properties": {
                                    "rating": {
                                        "type": "integer"
                                    },
                                    "comment": {
                                        "oneOf": [
                                            {
                                                "type": "string"
                                            },
                                            {
                                                "type": "null"
                                            }
                                        ]
                                    },
                                    "at": {
                                        "type": "string"
                                    }
                                }
                            },
                            {
                                "type": "null"
                            }
                        ]
                    },
                    "replies": {
                        "type": "array",
                        "description": "Only when reading a single ticket",
                        "items": {
                            "type": "object",
                            "properties": {
                                "from": {
                                    "type": "string",
                                    "enum": [
                                        "support",
                                        "customer"
                                    ]
                                },
                                "message": {
                                    "type": "string"
                                },
                                "at": {
                                    "type": "string"
                                }
                            }
                        }
                    }
                }
            },
            "TicketEnvelope": {
                "type": "object",
                "properties": {
                    "ok": {
                        "type": "boolean"
                    },
                    "replayed": {
                        "type": "boolean",
                        "description": "Present when raising a ticket"
                    },
                    "ticket": {
                        "$ref": "#/components/schemas/Ticket"
                    }
                }
            },
            "Feedback": {
                "type": "object",
                "required": [
                    "rating"
                ],
                "properties": {
                    "rating": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 5
                    },
                    "comment": {
                        "type": "string",
                        "maxLength": 2000
                    }
                }
            },
            "WebhookEvent": {
                "type": "object",
                "properties": {
                    "event": {
                        "type": "string",
                        "enum": [
                            "ticket.replied",
                            "ticket.resolved",
                            "ticket.closed",
                            "ticket.reopened",
                            "ticket.status_changed",
                            "ticket.feedback"
                        ]
                    },
                    "sent_at": {
                        "type": "string"
                    },
                    "ticket": {
                        "$ref": "#/components/schemas/Ticket"
                    },
                    "data": {
                        "type": "object",
                        "description": "ticket.replied: message. ticket.feedback and reopened by rating: rating, comment.",
                        "properties": {
                            "message": {
                                "type": "string"
                            },
                            "rating": {
                                "type": "integer"
                            },
                            "comment": {
                                "type": "string"
                            }
                        }
                    }
                }
            },
            "InquiryRequest": {
                "type": "object",
                "required": [
                    "name",
                    "email",
                    "message",
                    "consent",
                    "_t",
                    "cf-turnstile-response"
                ],
                "properties": {
                    "name": {
                        "type": "string",
                        "description": "2 to 120 characters, no links or markup"
                    },
                    "email": {
                        "type": "string",
                        "format": "email"
                    },
                    "phone": {
                        "type": "string"
                    },
                    "company": {
                        "type": "string"
                    },
                    "service": {
                        "type": "string",
                        "description": "One from the config list, otherwise Other"
                    },
                    "message": {
                        "type": "string",
                        "description": "10 to 3,000 characters, at most 2 links"
                    },
                    "consent": {
                        "type": "string",
                        "enum": [
                            "1"
                        ],
                        "description": "Agreement to be contacted"
                    },
                    "marketing": {
                        "type": "string",
                        "enum": [
                            "0",
                            "1"
                        ],
                        "description": "Agreed to greetings and offers"
                    },
                    "_t": {
                        "type": "string",
                        "description": "When the form was shown, in milliseconds since 1970. A form sent in under 3 seconds is refused."
                    },
                    "cf-turnstile-response": {
                        "type": "string",
                        "description": "Cloudflare Turnstile token"
                    },
                    "company_website": {
                        "type": "string",
                        "description": "Hidden trap field: must be empty"
                    }
                }
            },
            "HeartbeatRequest": {
                "type": "object",
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": [
                            "ok",
                            "degraded",
                            "down"
                        ],
                        "default": "ok",
                        "description": "degraded = running but impaired; down = something critical is broken"
                    },
                    "version": {
                        "type": "string",
                        "maxLength": 40,
                        "description": "What the system runs, compared with the upgrade log"
                    },
                    "environment": {
                        "type": "string",
                        "maxLength": 20
                    },
                    "errors_24h": {
                        "type": "integer",
                        "minimum": 0,
                        "description": "Errors logged in the last 24 hours"
                    },
                    "active_users": {
                        "type": "integer",
                        "minimum": 0
                    },
                    "message": {
                        "type": "string",
                        "maxLength": 255,
                        "description": "A sentence shown to staff, mainly with degraded or down"
                    },
                    "metrics": {
                        "type": "object",
                        "description": "Up to 20 small values. Keys: letters, numbers, . _ - ; values: numbers, booleans or text up to 100 characters",
                        "additionalProperties": true
                    }
                }
            },
            "Licence": {
                "type": "object",
                "required": [
                    "ok",
                    "linked",
                    "entitled"
                ],
                "properties": {
                    "ok": {
                        "const": true
                    },
                    "linked": {
                        "type": "boolean",
                        "description": "False until staff link this key to a subscription"
                    },
                    "entitled": {
                        "type": [
                            "boolean",
                            "null"
                        ],
                        "description": "True while trial, active or pending_renewal; false when suspended, expired or cancelled; null when not linked"
                    },
                    "status": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "The subscription status, or \"exempt\" for HQ and demo accounts"
                    },
                    "subscription": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Subscription number"
                    },
                    "plan": {
                        "type": "string"
                    },
                    "renewal_date": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date"
                    },
                    "days_left": {
                        "type": [
                            "integer",
                            "null"
                        ],
                        "description": "Negative once past renewal"
                    },
                    "note": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "plan_code": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Short code of the plan, for example starter or pro"
                    },
                    "limits": {
                        "type": "object",
                        "description": "What the plan allows (for example members, sms_per_month, white_label). Waesta sets them per plan; the connected system enforces them",
                        "additionalProperties": true
                    },
                    "pending_change": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "description": "A scheduled plan change (usually a downgrade at the next renewal)",
                        "properties": {
                            "plan_code": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            },
                            "plan": {
                                "type": "string"
                            },
                            "effective_date": {
                                "type": "string",
                                "format": "date"
                            },
                            "limits": {
                                "type": "object",
                                "additionalProperties": true
                            },
                            "billing_frequency": {
                                "type": [
                                    "string",
                                    "null"
                                ]
                            }
                        }
                    },
                    "exempt": {
                        "type": "boolean",
                        "description": "True for Waesta HQ and demo accounts, which need no licence: entitled is then always true"
                    },
                    "account_kind": {
                        "type": "string",
                        "enum": [
                            "client",
                            "internal",
                            "demo"
                        ]
                    },
                    "billing_frequency": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "monthly",
                            "quarterly",
                            "semi_annual",
                            "annual",
                            "biennial",
                            null
                        ],
                        "description": "How often the client is billed"
                    }
                }
            },
            "HeartbeatResponse": {
                "type": "object",
                "required": [
                    "ok"
                ],
                "properties": {
                    "ok": {
                        "const": true
                    },
                    "server_time": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "latest_version": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "description": "Newest version logged for this system"
                    },
                    "update_available": {
                        "type": "boolean"
                    },
                    "licence": {
                        "type": "object",
                        "description": "Same fields as the Licence reply, without ok"
                    }
                }
            },
            "SmsRates": {
                "type": "object",
                "properties": {
                    "ok": {
                        "type": "boolean"
                    },
                    "default_country": {
                        "type": "string",
                        "enum": [
                            "UG",
                            "KE",
                            "TZ"
                        ]
                    },
                    "countries": {
                        "type": "object",
                        "description": "Countries we can send to, keyed by ISO code",
                        "additionalProperties": {
                            "type": "object",
                            "properties": {
                                "name": {
                                    "type": "string"
                                },
                                "credits_per_part": {
                                    "type": "integer",
                                    "description": "Credits of that wallet one message part costs for this destination (normally 1)"
                                },
                                "wallet": {
                                    "type": "string",
                                    "enum": [
                                        "local",
                                        "international"
                                    ],
                                    "description": "local = Uganda credits, international = International credits"
                                }
                            }
                        }
                    }
                }
            },
            "PlanOption": {
                "type": "object",
                "properties": {
                    "plan_code": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "name": {
                        "type": "string"
                    },
                    "price": {
                        "type": "string",
                        "description": "What this client would pay per period"
                    },
                    "currency": {
                        "type": "string"
                    },
                    "billing_frequency": {
                        "type": "string"
                    },
                    "limits": {
                        "type": "object",
                        "additionalProperties": true
                    },
                    "current": {
                        "type": "boolean"
                    },
                    "change": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "upgrade",
                            "downgrade",
                            "change",
                            null
                        ]
                    },
                    "selectable": {
                        "type": "boolean",
                        "description": "False for the current plan and for plans staff must price (another currency)"
                    },
                    "period_change": {
                        "type": "boolean",
                        "description": "True when moving there changes how often the client is billed (starts at the next renewal)"
                    }
                }
            },
            "Plans": {
                "type": "object",
                "properties": {
                    "ok": {
                        "const": true
                    },
                    "linked": {
                        "type": "boolean"
                    },
                    "self_service": {
                        "type": "boolean"
                    },
                    "subscription": {
                        "type": "string"
                    },
                    "plans": {
                        "type": "array",
                        "items": {
                            "$ref": "#/components/schemas/PlanOption"
                        }
                    }
                }
            },
            "PlanChangeRequest": {
                "type": "object",
                "required": [
                    "plan_code"
                ],
                "properties": {
                    "plan_code": {
                        "type": "string"
                    },
                    "preview": {
                        "type": "boolean",
                        "description": "True shows the effect and changes nothing"
                    },
                    "when": {
                        "type": "string",
                        "enum": [
                            "auto",
                            "renewal"
                        ],
                        "default": "auto"
                    },
                    "billing_frequency": {
                        "type": "string",
                        "enum": [
                            "monthly",
                            "quarterly",
                            "semi_annual",
                            "annual",
                            "biennial"
                        ],
                        "description": "Which billing period of the plan_code you mean; default is the period the client is on"
                    }
                }
            },
            "PlanChange": {
                "type": "object",
                "properties": {
                    "ok": {
                        "const": true
                    },
                    "preview": {
                        "type": "boolean"
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "applied",
                            "scheduled"
                        ]
                    },
                    "change": {
                        "type": "object",
                        "properties": {
                            "kind": {
                                "type": "string"
                            },
                            "effective_date": {
                                "type": "string",
                                "format": "date"
                            },
                            "old_price": {
                                "type": "string"
                            },
                            "new_price": {
                                "type": "string"
                            },
                            "currency": {
                                "type": "string"
                            },
                            "billed_now": {
                                "type": "string",
                                "description": "Prorated difference invoiced now for an upgrade, else 0.00"
                            },
                            "from": {
                                "type": "object"
                            },
                            "to": {
                                "type": "object"
                            }
                        }
                    },
                    "invoice": {
                        "type": [
                            "object",
                            "null"
                        ],
                        "properties": {
                            "invoice_no": {
                                "type": "string"
                            },
                            "total": {
                                "type": "string"
                            },
                            "currency": {
                                "type": "string"
                            },
                            "due_date": {
                                "type": "string"
                            },
                            "pay_url": {
                                "type": "string"
                            }
                        }
                    },
                    "licence": {
                        "$ref": "#/components/schemas/Licence"
                    }
                }
            }
        }
    }
}