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.
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.
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." }'
Node.js 18+
const res = await fetch('https://kainetgen.com/bulk-sms/api/v1/sms/send', {
method: 'POST',
headers: {
'X-API-Key': process.env.KAINETGEN_SMS_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ to: '+256772123456', message: 'Your order is ready for collection.' }),
});
const { success, data, error } = await res.json();
Python 3
import os, requests
res = requests.post(
"https://kainetgen.com/bulk-sms/api/v1/sms/send",
headers={"X-API-Key": os.environ["KAINETGEN_SMS_KEY"]},
json={"to": "+256772123456", "message": "Your order is ready for collection."},
timeout=30,
)
body = res.json()
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.
Scope
Allows
sms:send
POST /sms/send, POST /sms/bulk, POST /sms/estimate
sms:read
GET /sms/status/{id}
balance:read
GET /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
Field
Type
Description
to
string or array
One 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.
messagerequired
string
The text, up to 10 SMS segments. Use {{first_name}}-style variables when sending to saved contacts.
senderId
string
An approved sender ID for your organization. Defaults to your default sender ID.
contactIds, groupIds
array
Send to saved contacts (up to 1,000) or groups (up to 50).
scheduleAt
ISO 8601 date
Send later instead of now. Must be in the future. Optional timezone (IANA name).
appendOptOut
boolean
Add the opt-out line. Defaults to your organization setting.
reference
string
Your own reference (up to 100 characters), shown in reports.
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.
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.
Accepted and waiting to be handed to the SMS network.
SENT
Handed to the network; no delivery report yet.
DELIVERED
The network reported delivery to the handset.
FAILED / REJECTED
Not delivered. See errorCode. Unsent messages are refunded.
UNKNOWN
The 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.
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
Limit
Value
Send requests per API key
120 per minute
Send requests per organization (all keys and users)
300 per minute
All requests per client IP
600 per minute
Numbers in to per request
1,000
Recipients per campaign
250,000
Message length
10 segments (1,530 GSM-7 characters)
Daily messages
Per-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
HTTP
Code
When
400
BAD_JSON, BAD_REQUEST
The body isn’t valid JSON, or the Idempotency-Key is malformed.
401
UNAUTHORIZED
Missing, invalid, revoked or expired key.
402
INSUFFICIENT_CREDITS
Not enough SMS credits. Nothing is sent or charged.
403
FORBIDDEN
The key lacks the scope for this endpoint.
404
NOT_FOUND
No such message or batch in your organization.
409
DUPLICATE_MESSAGE, IDEMPOTENCY_IN_PROGRESS
Same text to the same recipients within one minute, or a request with the same Idempotency-Key is still being processed.
413
TOO_LARGE
The request body exceeds 2 MB.
422
VALIDATION_ERROR
A field is missing or invalid; details lists each field.
The request is well-formed but can’t be sent as it stands.
429
RATE_LIMITED, DAILY_LIMIT
Too many requests, or the daily sending limit would be exceeded.
503
UNAVAILABLE
Temporarily 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.