WWaesta Enterprises U Ltd +256750081491 [email protected]
Base address for every call: https://waestaos.waestaltd.com

Waesta OS API: start here

Four small JSON APIs let your own systems (Benofy, Webook, EduMaster, a client's website...) work with Waesta OS. Together they link a system fully: it reports its health, checks its subscription, raises support tickets, and sells or sends SMS, with no manual steps.

Machine-readable description for Postman, Insomnia, Swagger UI and code generators: openapi.json (OpenAPI 3.1). Use *Download OpenAPI file* at the top of this page: its default server is already this system's address. In Postman: *Import → File*, then add your key.

The examples use the address of this system. In a copy read outside Waesta OS they show YOUR-SYSTEM where the address goes: replace it with yours (for example os.waestaltd.com).


1. Quick start (5 minutes)

Get a key. Keys are created in the system and shown once:

  • SMS: *SMS → Customers → the customer → API keys* (or the customer does it in the portal under *SMS credits → Connect your system*).
  • Support, heartbeat and licence (one key for all three): *Customer care → Systems → the system → Connect this system (API)*. Link it to the client's subscription there so the licence check works.

What to wire up first in every system we build: a heartbeat job (health and version), a licence check (is the subscription current?), the support ticket call behind your "Report a problem" button with its webhook receiver, and the SMS API if it sends or sells SMS. SYSTEMS_API.md has an integration checklist.

Try it (replace the key):

# SMS balance
curl https://waestaos.waestaltd.com/api/v1/sms/balance \
  -H "Authorization: Bearer wsk_YOUR_KEY"

# Send an SMS
curl -X POST https://waestaos.waestaltd.com/api/v1/sms/send \
  -H "Authorization: Bearer wsk_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"to":["0772345678"],"message":"Your code is 4821"}'

# Tell Waesta OS this system is alive (send every 1 to 5 minutes from a scheduled job)
curl -X POST https://waestaos.waestaltd.com/api/v1/systems/heartbeat \
  -H "Authorization: Bearer wcp_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"status":"ok","version":"2.4.1","errors_24h":0}'

# Is this system's subscription current?
curl https://waestaos.waestaltd.com/api/v1/subscription -H "Authorization: Bearer wcp_YOUR_KEY"

# Raise a support ticket
curl -X POST https://waestaos.waestaltd.com/api/v1/support/tickets \
  -H "Authorization: Bearer wcp_YOUR_KEY" -H "Content-Type: application/json" \
  -d '{"subject":"Report will not export","message":"Export does nothing.","external_ref":"BEN-4471","reporter":{"name":"Grace","email":"[email protected]"}}'

A working call answers {"ok": true, ...}. Anything else answers {"ok": false, "error": "what to fix"} with an HTTP status from the table below.


2. Rules that apply to every API

Authentication

Send the key in a header on every call:

Authorization: Bearer wsk_...        (SMS)   or   Authorization: Bearer wcp_...   (Support)

If your host strips the Authorization header, send X-Api-Key: <key> instead. Keys are different per API: an SMS key does not work on the support or systems API. The support key (wcp_) also serves the systems and licence API.

  • Keep keys on your server. Never put one in a web page, a mobile app or a public repository.
  • Use one key per system and environment (for example "Benofy production", "Benofy test") so you can revoke one without touching the others.
  • If a key leaks, revoke it in the system and create a new one. Revoked keys are refused immediately.

Requests and responses

  • HTTPS only. Send Content-Type: application/json and a JSON body for POST. Responses are JSON.
  • Times are ISO 8601 in UTC (for example 2026-09-28T08:00:00+00:00).
  • Field names are snake_case. Unknown fields are ignored; missing optional fields use safe defaults.

Success and errors

Every reply has ok. On failure error is a sentence you can log or show to a developer.

{ "ok": false, "error": "There is no valid phone number to send to." }

Rate limits

  • 120 requests per minute per key, and 600 per minute per network address.
  • 30 wrong keys from one address in 10 minutes are blocked for a while.
  • Bulk work belongs in one call with many recipients (SMS: up to 1,000 numbers), not in many small calls.

Retrying safely (idempotency)

Networks fail. Make retries harmless:

Retry 429, 503 and network errors with a growing delay (1s, 5s, 30s...). Do not blindly retry 4xx errors: fix the request.

Webhooks (support API)

When we reply, resolve, close or re-open a ticket your system can be called. Every call is signed:

X-Waesta-Signature: sha256=<HMAC-SHA256 of the raw body with your webhook secret>
X-Waesta-Event:     ticket.resolved
X-Waesta-Delivery:  1234   (unique id: ignore repeats)

Verify the signature before trusting a call, and answer with any 2xx. Details and a receiver example: SUPPORT_API.md.


3. Examples in other languages

PHP

function waesta(string $method, string $path, string $key, ?array $body = null): array {
    $ch = curl_init('https://waestaos.waestaltd.com' . $path);
    curl_setopt_array($ch, [
        CURLOPT_CUSTOMREQUEST => $method, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 20,
        CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $key, 'Content-Type: application/json'],
        CURLOPT_POSTFIELDS => $body === null ? null : json_encode($body),
    ]);
    $reply = json_decode((string) curl_exec($ch), true) ?: ['ok' => false, 'error' => 'No valid reply'];
    $reply['_status'] = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
    return $reply;
}
$r = waesta('POST', '/api/v1/sms/send', $smsKey, ['to' => ['0772345678'], 'message' => 'Hello']);

Node.js (18+, run on your server)

async function waesta(method, path, key, body) {
  const res = await fetch('https://waestaos.waestaltd.com' + path, {
    method, headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
    body: body ? JSON.stringify(body) : undefined,
  });
  return { status: res.status, ...(await res.json()) };
}
const r = await waesta('POST', '/api/v1/support/tickets', supportKey,
  { subject: 'Cannot log in', message: 'Error on the login page', external_ref: 'WEB-88' });

Python

import requests
def waesta(method, path, key, body=None):
    r = requests.request(method, "https://waestaos.waestaltd.com" + path, json=body, timeout=20,
                         headers={"Authorization": f"Bearer {key}"})
    return {"status": r.status_code, **r.json()}
r = waesta("GET", "/api/v1/sms/balance", sms_key)

Verify a webhook (Node.js / Express)

const crypto = require('crypto');
app.post('/hooks/waesta', express.raw({ type: '*/*' }), (req, res) => {
  const expected = 'sha256=' + crypto.createHmac('sha256', process.env.WAESTA_HOOK_SECRET).update(req.body).digest('hex');
  const given = req.get('X-Waesta-Signature') || '';
  if (given.length !== expected.length || !crypto.timingSafeEqual(Buffer.from(given), Buffer.from(expected))) return res.sendStatus(401);
  const event = JSON.parse(req.body);   // event.event, event.ticket.external_ref ...
  res.sendStatus(204);
});

4. Going live checklist

  • ☐ Key is stored in the server's environment or secret store, not in code.
  • ☐ Requests use HTTPS and a timeout (15 to 20 seconds).
  • ☐ SMS: you send an Idempotency-Key and handle 402 (low credits) by alerting your team.
  • ☐ Support: you send external_ref and store the returned ticket_no next to your own record.
  • ☐ Heartbeat: a scheduled job sends it every 1 to 5 minutes with version; the licence answer is cached and entitled: false is handled kindly (see SYSTEMS_API.md).
  • ☐ Webhooks: the receiver verifies the signature, answers fast with 2xx, and ignores repeated X-Waesta-Delivery ids.
  • ☐ You log every non-2xx reply including its error text.
  • ☐ Test with a test key first, then create a separate production key.

5. Website inquiry form

The contact form on waestaltd.com posts to the system so every message becomes an inquiry, a prospect client and a lead. You normally just paste the two-line embed code from *Website inquiries* into the page; this section is for developers who build their own form.

GET /api/inquiry/config returns the Turnstile site key and the list of services.

POST /api/inquiry (form-encoded or JSON), only from the allowed website origins (INQUIRY_ALLOWED_ORIGINS):

Answers {"ok": true, "reference": "INQ-00012"}. Limits: 20 attempts per hour and 5 accepted inquiries per hour per address, and 3 per day per email.


6. Changes