Products

Bulk SMS PlatformBusiness messaging at scale Core Banking SystemFor SACCOs and cooperatives
SMS platform sign-in Book a consultation

Developer API

Connect your systems. Send with confidence.

Integrate Kainetgen Bulk SMS into your applications, websites and business systems with a REST API: queue single or bulk messages, check their status and read your balance.

Base URLhttps://kainetgen.com/bulk-sms/api/v1
FormatJSON over HTTPS
AuthX-API-Key

Quick start

  1. Get an account. Create an organization or ask us to set one up, and make sure it has SMS credits.
  2. Create an API key. In the dashboard open Account → API (Owners and Administrators). Choose the scopes it needs. The key is shown once: store it in your server’s secret store.
  3. Send your first message.
Shell
curl -X POST "https://kainetgen.com/bulk-sms/api/v1/sms/send" \
  -H "X-API-Key: ${KAINETGEN_SMS_KEY}" \
  -H "Content-Type: application/json" \
  -d '{ "to": "+256772123456", "message": "Your order is ready for collection." }'

Sends are queued, not instant. A successful send returns 202 Accepted with an ID. The message is then handed to the SMS network in the background; use message status to follow it to DELIVERED.

All responses use one envelope: { "success": true, "data": …, "message": "…" } or, on error, { "success": false, "error": "…", "code": "…" }.

Authentication

Every request needs an API key, sent in the X-API-Key header (or as Authorization: Bearer kg_live_…). Keys start with kg_live_.

  • Shown once. The full key appears only when it’s created. The dashboard afterwards shows just its first characters.
  • Stored as a keyed hash. Kainetgen keeps an HMAC-SHA-256 of each key, never the key itself.
  • Revocable and optionally expiring. Revoking a key in the dashboard stops it on the very next request.
  • Tied to a person. A key stops working if the person who created it is deactivated or leaves the organization, or if the organization is suspended.
  • Paid from its creator’s balance. Messages sent with a key use the SMS credits assigned to the user who created it.

Scopes

Give each key only the scopes it needs. Every request is checked against the key’s scopes on the server.

ScopeAllows
sms:sendPOST /sms/send, POST /sms/bulk, POST /sms/estimate
sms:readGET /sms/status/{id}
balance:readGET /balance

API keys can only reach these endpoints. Contacts, users, settings and other dashboard functions are not available to keys.

POST /sms/send

Send one message to one or more recipients. Requires sms:send.

Request body

FieldTypeDescription
tostring or arrayOne number, a comma-separated string, or an array of up to 1,000 numbers. International (+256…) or local format. Duplicates, invalid and opted-out numbers are removed.
message requiredstringThe text, up to 10 SMS segments. Use {{first_name}}-style variables when sending to saved contacts.
senderIdstringAn approved sender ID for your organization. Defaults to your default sender ID.
contactIds, groupIdsarraySend to saved contacts (up to 1,000) or groups (up to 50).
scheduleAtISO 8601 dateSend later instead of now. Must be in the future. Optional timezone (IANA name).
appendOptOutbooleanAdd the opt-out line. Defaults to your organization setting.
referencestringYour own reference (up to 100 characters), shown in reports.

Response 202 Accepted

JSON
{
  "success": true,
  "message": "SMS queued successfully",
  "data": {
    "id": "cm1x7k2p40001…",
    "batchId": "cm1x7k2p40001…",
    "status": "QUEUED",
    "recipients": 1,
    "segments": 1,
    "credits": 1,
    "scheduledAt": null,
    "validation": { "totalRows": 1, "valid": 1, "duplicates": 0, "invalid": 0, "suppressed": 0, "readyToSend": 1 }
  }
}

Credits for the whole send are reserved up front, and refunded for any message that is not sent. The same text to the same recipients within one minute is rejected as a duplicate (409 DUPLICATE_MESSAGE).

POST /sms/bulk

Create and queue a bulk campaign. Accepts every field of /sms/send, plus name (up to 120 characters) and description (up to 500), and is listed as a campaign in the dashboard. Requires sms:send.

Request
curl -X POST "https://kainetgen.com/bulk-sms/api/v1/sms/bulk" \
  -H "X-API-Key: ${KAINETGEN_SMS_KEY}" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: agm-notice-2026-10" \
  -d '{
    "name": "AGM notice",
    "groupIds": ["cm1grp…"],
    "message": "Dear {{first_name}}, our AGM is on 30 Oct at 10am.",
    "scheduleAt": "2026-10-20T07:00:00Z"
  }'

Returns 202 with the same shape as /sms/send. A campaign can reach up to 250,000 recipients through groups and contacts; the to array itself is limited to 1,000 numbers per request.

POST /sms/estimate

Validate recipients and price a message without sending anything. Requires sms:send.

Request body
{
  "message": "Your statement is ready.",
  "audience": { "phones": ["+256772123456", "0701123456"] }
}

The response includes the encoding (GSM-7 or UCS-2), segments per message, total credits, your available credits and whether the balance is sufficient, plus a sample of invalid numbers.

GET /sms/status/{id}

Status of a single message, or of every message in a send when given the batch id returned by /sms/send or /sms/bulk. Requires sms:read. Only messages belonging to your organization are returned; any other ID gives 404.

Response (batch)
{
  "success": true,
  "data": {
    "type": "batch",
    "batch": { "id": "cm1x7k…", "status": "COMPLETED", "recipients": 2, "sent": 2, "delivered": 1, "failed": 0, "scheduledAt": null },
    "messages": [
      { "id": "cm1msg…", "to": "+256772123456", "status": "DELIVERED", "segments": 1, "errorCode": null,
        "sentAt": "2026-10-09T09:00:02Z", "deliveredAt": "2026-10-09T09:00:07Z", "createdAt": "2026-10-09T09:00:00Z" }
    ],
    "truncated": false
  }
}
Message statusMeaning
QUEUEDAccepted and waiting to be handed to the SMS network.
SENTHanded to the network; no delivery report yet.
DELIVEREDThe network reported delivery to the handset.
FAILED / REJECTEDNot delivered. See errorCode. Unsent messages are refunded.
UNKNOWNThe network gave no final report.

Batches move through SCHEDULED, QUEUED, SENDING and finish as COMPLETED, PARTIALLY_COMPLETED, FAILED or CANCELLED. Up to 1,000 messages are listed per batch (truncated tells you if there are more). Delivery updates are available by polling this endpoint; there are no outgoing callbacks to your systems.

GET /balance

The SMS balance available to the key (its creator’s credits). Requires balance:read.

Response
{ "success": true, "data": { "credits": 48250, "sms": 48250, "lowBalanceThreshold": 1000, "unit": "SMS" } }

Safe retries with Idempotency-Key

Networks fail. If a request times out you can’t tell whether it was accepted, and sending it again could message (and charge) everyone twice. Add an Idempotency-Key header to /sms/send and /sms/bulk:

  • 8–100 characters: letters, digits, . _ : -. Use something unique to the action, such as order-1042-ready.
  • Repeating a request with the same key within 24 hours returns the original result with 200 and the header Idempotent-Replayed: true. Nothing is sent or charged again.
  • Reusing a key with a different request body returns 422 IDEMPOTENCY_MISMATCH.
  • Keys are private to your organization.

Limits & quotas

LimitValue
Send requests per API key120 per minute
Send requests per organization (all keys and users)300 per minute
All requests per client IP600 per minute
Numbers in to per request1,000
Recipients per campaign250,000
Message length10 segments (1,530 GSM-7 characters)
Daily messagesPer-organization daily limit, set by Kainetgen

Rate-limited responses are 429 with RateLimit headers telling you when to retry. A GSM-7 message holds 160 characters (153 per part when split); messages with characters outside GSM-7, such as most emoji, use Unicode at 70 (67 per part). See SMS segments explained.

Errors

HTTPCodeWhen
400BAD_JSON, BAD_REQUESTThe body isn’t valid JSON, or the Idempotency-Key is malformed.
401UNAUTHORIZEDMissing, invalid, revoked or expired key.
402INSUFFICIENT_CREDITSNot enough SMS credits. Nothing is sent or charged.
403FORBIDDENThe key lacks the scope for this endpoint.
404NOT_FOUNDNo such message or batch in your organization.
409DUPLICATE_MESSAGE, IDEMPOTENCY_IN_PROGRESSSame text to the same recipients within one minute, or a request with the same Idempotency-Key is still being processed.
413TOO_LARGEThe request body exceeds 2 MB.
422VALIDATION_ERRORA field is missing or invalid; details lists each field.
422NO_RECIPIENTS, SENDER_NOT_APPROVED, MESSAGE_TOO_LONG, TOO_MANY_RECIPIENTS, IDEMPOTENCY_MISMATCHThe request is well-formed but can’t be sent as it stands.
429RATE_LIMITED, DAILY_LIMITToo many requests, or the daily sending limit would be exceeded.
503UNAVAILABLETemporarily unavailable. Retry with the same Idempotency-Key.

Error messages are written for people and never include internal details. Branch on code, not on the message text.

Security practices

  • Server-side only. Call the API from your server. Never put an API key in browser JavaScript, a mobile app bundle or public source code: anyone could extract it and send at your expense.
  • Use environment variables or a secret manager for keys, as in the examples above, and keep them out of version control and logs.
  • One key per system, least privilege. A reporting job only needs sms:read or balance:read.
  • Rotate and revoke. Create the replacement, deploy it, then revoke the old key. Revoke immediately if a key may have leaked.
  • Use Idempotency-Key on every send your code might retry.

Ready to integrate?

Create a key in the dashboard, or talk to us about volumes, sender IDs and pricing.