Two ways to integrate
Pick the path that fits your project. Both talk to the same REST/JSON API.
Use an official SDK
Production-tested client libraries covering almost every language. Auth, phone normalization, message cleaning, and bulk chunking are handled for you. Install with your language's package manager and send in three lines.
Call the REST API directly
One HTTPS POST with a JSON body. Works in any language with an HTTP client. Use this when your language isn't covered above, when you need custom wiring, or when you want full control. The rest of this page is the reference for Path B.
Requirements
What you need before your first call.
Active kwtSMS.com account with API enabled.
API username and password from the API information page in your account.
HTTPS-capable client (all major languages and frameworks).
Request headers: Content-Type: application/json, method POST.
Quickstart
Four steps. Copy, paste, send.
1Get your credentials
Sign in to your kwtSMS account and open the API information page. Copy your API username and API password.
Your API username is not your mobile number or account login. It's issued separately on the API information page.
2Set headers
3Send your first SMS
4Read the response
Save the msg-id. You'll need it to poll status or DLR later.
Must-know information
The operational facts that bite integrators who skip reading.
Promotional SenderID (KWT-SMS) is for testing only
Delivery is intentionally throttled (~100s+) and blacklisted or Virgin numbers won't receive. For production, register a private senderid. See Promotional vs Transactional SenderID and SenderID help.
Links in messages
International carriers filter links to protect against fraud. Kuwait is fine; messages with links to other countries may be blocked.
Sending speed and rate limits
Max 5 requests per second per IP. Exceed and your IP is blocked automatically. Recommended: 2 per second with a small delay.
International coverage
Disabled by default. Contact support to activate specific destination countries on your account.
Server timezone
Asia/Kuwait (GMT+3). All timestamps in responses use server time.
Security baseline
Always HTTPS. Never hardcode credentials. API lockdown (IP allowlist) is available in the dashboard as an optional second layer.
API reference
All seven endpoints. Every parameter, every response. All calls are POST with Content-Type: application/json.
Send
Sends an SMS to one or many Kuwait-format numbers. Auto-detects language (English GSM-7 or Arabic UCS-2). Returns a unique msg-id for status and DLR lookups.
| Parameter | Type | Max | Description |
|---|---|---|---|
| usernamereq | alphanumeric | 15 | API username. |
| passwordreq | alphanumeric | 30 | API password. |
| senderreq | alphanumeric | 11 | Private SenderID (case-sensitive). |
| mobilereq | numeric | 2500 | Comma-separated Kuwait numbers. No +, 00, (), . or spaces. |
| messagereq | alphanumeric | no limit | Message body. Use \n for new lines. No HTML or emoji. |
| test | numeric | 1 | 0 or 1. If 1, message queued but not sent (saves test credits). |
Balance
Returns the account's current SMS credit balance and total credits purchased. Every send response also returns balance-after, so you rarely need this call on its own.
| Parameter | Type | Max | Description |
|---|---|---|---|
| usernamereq | alphanumeric | 15 | API username. |
| passwordreq | alphanumeric | 30 | API password. |
Sender IDs
Returns the list of sender IDs your account can send from. Use this to build a dropdown or to validate the sender string before calling send.
| Parameter | Type | Max | Description |
|---|---|---|---|
| usernamereq | alphanumeric | 15 | API username. |
| passwordreq | alphanumeric | 30 | API password. |
Coverage
Returns the country prefixes active on your account. Call this when a send fails with ERR026 to confirm the destination country is activated.
| Parameter | Type | Max | Description |
|---|---|---|---|
| usernamereq | alphanumeric | 15 | API username. |
| passwordreq | alphanumeric | 30 | API password. |
Validate
Checks numbers for format validity and route availability before you attempt to send. Returns three buckets: OK (good), ER (format error, typically a + or 00 prefix), and NR (no route, country not activated).
| Parameter | Type | Max | Description |
|---|---|---|---|
| usernamereq | alphanumeric | 15 | API username. |
| passwordreq | alphanumeric | 30 | API password. |
| mobilereq | numeric | 2500 | Comma-separated numbers to validate. |
Status
Returns the queue status of a sent message: pending, error in queue, or sent to gateway. This is not a delivery report; it tells you whether the gateway has dispatched the message.
| Parameter | Type | Max | Description |
|---|---|---|---|
| usernamereq | alphanumeric | 15 | API username. |
| passwordreq | alphanumeric | 30 | API password. |
| msgidreq | alphanumeric | 32 | The msg-id returned by the earlier send call. |
Delivery report (DLR)
Delivery reports for international messages. Kuwait numbers don't return DLR. Wait at least 5 minutes after sending before calling, and add a delay between requests if you poll in bulk, or your IP may be blocked.
| Parameter | Type | Max | Description |
|---|---|---|---|
| usernamereq | alphanumeric | 15 | API username. |
| passwordreq | alphanumeric | 30 | API password. |
| msgidreq | alphanumeric | 32 | The msg-id returned by the earlier send call. |
Error codes
All 33 error responses. Every code returned by every endpoint. Errors follow the shape {"result":"ERROR","code":"ERRxxx","description":"…"}.
| Code | Function | Description |
|---|---|---|
| ERR001 | ALL | API off on this account. |
| ERR002 | ALL | Username, password, or other required parameter is missing. |
| ERR003 | ALL | Wrong username or password. |
| ERR004 | ALL | Your account does not have API access. |
| ERR005 | ALL | Your account has been blocked. |
| ERR006 | send | No valid numbers submitted. |
| ERR007 | send | Cannot send more than 200 numbers at a time. |
| ERR008 | send | The senderid chosen is banned. |
| ERR009 | send | Message parameter is empty. |
| ERR010 | send | Your account balance is zero. |
| ERR011 | send | Not enough balance to send this message. |
| ERR012 | send | Cannot send more than 6 page messages (message too long). |
| ERR013 | send | Your queued messages reached 1000, wait and try again. |
| ERR019 | dlr | No reports found. |
| ERR020 | dlr | Message does not exist. |
| ERR021 | dlr | Message does not have a delivery report. |
| ERR022 | dlr | Delivery reports not ready. Check back after 24 hours. |
| ERR023 | dlr | Unknown error. Could not get delivery reports. |
| ERR024 | ALL | API lockdown is ON and the IP address is not in the allowed list. |
| ERR025 | send | No valid numbers found. Must be digits only with country code. No +, 00, (), . or space. |
| ERR026 | send | No route for this number. Country not activated. Contact support to add. |
| ERR027 | send | HTML tags not allowed. |
| ERR028 | send | Must wait 15 seconds before sending to the same number again. |
| ERR029 | status | Message does not exist or the msgid is wrong. |
| ERR030 | status | Message stuck in queue with error (delete to recover credits). |
| ERR031 | send | Bad language detected, send rejected. |
| ERR032 | send | Spam message detected, send rejected. |
| ERR033 | coverage | No active coverage on your account. Contact support to add one. |
Best practices
The checklist we hand production teams before go-live. Full write-up: SMS API implementation best practices.
Always use HTTPS
SSL encrypts your traffic and protects against eavesdropping.
Never hardcode credentials
Use environment variables or a config file so passwords can rotate without a deploy.
Test English and Arabic before handoff
If the client sends a language you didn't test, output can turn into gibberish.
Include the app name in OTP messages
Telecom rules require OTP and critical notifications identify the sender. Example: "Your OTP for APPNAME is 12345".
Allow 3 minutes before OTP resend
Gives users room to receive and read the SMS before they hit "Resend".
Sanitize user input, especially mobile numbers
Remove +, 00, spaces. Add country code if missing. Reject Arabic or Hindi digits.
Don't poll /balance/ after each send
The send response already returns points-charged and balance-after.
Stop bots and automation
Use captcha or similar on signup and login forms to prevent abuse and credit drainage.
Enforce your own rate limits
Per-IP and per-phone limits over time protect your balance and your users.
Delay between requests
Max 5 per second per IP. We recommend 2 per second in production.
Testing and verification
Before you flip to live mode, run every test below. Each row is "do this, expect this". Full write-up: SMS API integration test checklist.
Use "test":"1" on every send call until the last group. Test messages queue but don't deliver and don't burn credits. Delete them from the dashboard Queue to recover any held credit.
1Smoke test, one call per endpoint
| Do | Expect |
|---|---|
POST /API/balance/ with your API username and password. | {"result":"OK","available":N,"purchased":M} |
POST /API/senderid/ with the same credentials. | {"result":"OK","senderid":["KWT-SMS", ...]} |
POST /API/coverage/. | {"result":"OK","prefixes":["965", ...]} |
POST /API/validate/ with "mobile":"96598765432". | The number comes back inside the OK array. |
POST /API/send/ with "test":"1" to one of your own numbers. | {"result":"OK","msg-id":"...","numbers":1,...}. Save the msg-id. |
POST /API/status/ with the msgid you just saved. | {"result":"OK","status":"sent",...} |
2Phone number edge cases
| Mobile input | Expected behavior |
|---|---|
+96598765432 | Accepted. Your client must strip the + before posting. |
0096598765432 | Accepted. Your client must strip the leading 00. |
965 9876 5432 | Accepted. Your client must strip spaces. |
965-9876-5432 | Accepted. Your client must strip dashes. |
٩٦٥٩٨٧٦٥٤٣٢ (Arabic digits) | Convert to Latin digits before sending. API rejects Arabic digits. |
96522334455 (Kuwait landline) | API returns ERR025. No SMS sent, no credit charged. |
123456 (too short) | API returns ERR006 or ERR025. No SMS sent. |
abc123 (not a number) | API returns ERR025. No SMS sent. |
971504496677 (country not activated) | API returns ERR026. Contact support to enable coverage. |
3Send flow, end to end
| Do | Expect |
|---|---|
Send an English message with "test":"1". | result:"OK". Message appears in the dashboard Queue. Handset does NOT receive. |
| Delete the test message from the Queue. | Any held credit is refunded immediately. |
Send an Arabic message with "test":"0" (real send). | Handset receives within 60 seconds. Arabic displays correctly, not ???? or boxes. |
| Send an OTP message that includes your app name (e.g. "Your OTP for APPNAME is 123456"). | Handset receives. Message passes telecom compliance check. |
Poll /API/status/ with the msgid. | "status":"sent" once dispatched to carrier. |
| Send the same OTP to the same number twice in under 15 seconds. | Second call returns ERR028. First call is unaffected. |
4Error triggers, confirm your app handles each
| Do | Expect |
|---|---|
| Send with a wrong password. | ERR003. Your app shows a clear auth error, not a stack trace. |
Send with an empty message field. | ERR009. |
| Send to 201 numbers in one call. | ERR007. Chunk into batches of 200. |
| Send a message with HTML tags. | ERR027. Strip HTML before sending. |
| Send a message with emojis. | Often stuck silently in queue with no error. Strip emojis in your client. |
| Trigger rate limit: fire 10 sends per second from one IP. | IP auto-blocked. Keep sends at 2 per second or fewer. |
5Go-live blockers, fix before launch
| Issue | Severity | Check |
|---|---|---|
| SMS not arriving at all. | Critical | Ensure "test":"0", your private senderid is approved, and the number is not blacklisted. |
Arabic shows as ???? or boxes. | Critical | Post the body as UTF-8 JSON. Confirm your HTTP client doesn't re-encode. |
| No rate limiting on OTP endpoint. | Critical | Bots can drain your balance in minutes. Rate limit per phone (3 to 5 per hour) and per IP. |
| No CAPTCHA on signup or login OTP forms. | Critical | Add captcha before the SMS is sent, not after. |
| App name missing from OTP body. | High | Telecom compliance rule for OTP and critical alerts. |
| Phone format variants (+, 00, spaces) not handled. | High | Normalize on input: digits only, strip leading zeros. |
| OTP codes never expire. | High | Set a 3 to 5 minute TTL. |
| No resend countdown timer. | Medium | Block resend for 3 to 4 minutes so users don't spam. |
Using KWT-SMS in production. | Critical | Register a private senderid (and pick Transactional for OTP). |
FAQs
The questions support handles most days.
GET. Don't. Use POST with Content-Type: application/json and put the credentials in the JSON body.\n in the message field."test":"0", check Archive, verify number format, consider blacklist/DND, allow for campaign-season delays. Contact support with name, account, msg-id, timestamp, and message text.Talk to kwtSMS from any AI agent
kwtsms-cli exposes the full REST/JSON API as natural-language tool calls. Drop it into Claude Code, Cursor, or any MCP-compatible host, then say "send an OTP to …" and watch it happen.
Set up kwtsms-cli