SMS API · v1
Messages that Move Mountains
One HTTPS endpoint. Hashed API keys. JSON in, JSON out. Copy a snippet, try it live, then go to production.
curl -X POST "https://app.connectmedia.co.ke/api.php" \
-H "Authorization: Bearer $CM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "send",
"to": "254712345678",
"sender": "ConectMedia",
"message": "Hello from Connect Media"
}'
<?php
$ch = curl_init('https://app.connectmedia.co.ke/api.php');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('CM_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'action' => 'send',
'to' => '254712345678',
'sender' => 'ConectMedia',
'message' => 'Hello from Connect Media',
]),
]);
echo curl_exec($ch);
const res = await fetch('https://app.connectmedia.co.ke/api.php', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CM_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
action: 'send',
to: '254712345678',
sender: 'ConectMedia',
message: 'Hello from Connect Media',
}),
});
console.log(await res.json());
import os, requests
r = requests.post(
'https://app.connectmedia.co.ke/api.php',
headers={'Authorization': f"Bearer {os.environ['CM_API_KEY']}"},
json={
'action': 'send',
'to': '254712345678',
'sender': 'ConectMedia',
'message': 'Hello from Connect Media',
},
timeout=30,
)
print(r.json())
Trusted by teams that send at scale
Keys are bcrypt-hashed at rest, shown once, and revocable from the dashboard.
JSON or form POST. Four actions cover send, credits, history, and inbound SMS.
Import the spec into Postman, Insomnia, or generate a client in your language.
The Connect Media SMS API is a single HTTPS endpoint. Authenticate with an API key, pick an action, and parse the JSON envelope.
Register, then open Profile → API keys in the dashboard. Register · Manage keys
The raw 64-character hex key is shown once. Store it as CM_API_KEY. Hashes are stored server-side; we never keep the plaintext.
POST JSON to the production endpoint. Recipients must include the country code (E.164 without +).
export CM_API_KEY="your-64-char-hex-key"
curl -X POST "https://app.connectmedia.co.ke/api.php" \
-H "Authorization: Bearer $CM_API_KEY" \
-H "Content-Type: application/json" \
-d '{"action":"balance"}'
Every request requires a valid API key over TLS. Dashboard passwords are never accepted on the wire.
Authorization: Bearer YOUR_API_KEY
| Location | Name |
|---|---|
| Header | X-Api-Key |
| JSON or form body | api_key |
| Query string | api_key — avoid; keys leak in logs |
POST is recommended. GET is accepted for read actions. Send either application/json or application/x-www-form-urlencoded. All actions share this URL.
Responses are JSON. Treat the envelope code as the source of truth — HTTP status is typically 200 even when an application error is returned.
{
"code": "201",
"message": "Message Sent Successfully via Proxy!",
"data": {},
"api_version": "8.2.1"
}
| Action | Use | Success code |
|---|---|---|
send | Send SMS to one or more recipients | 201 |
balance | Read remaining credits | 200 |
history | List outbound messages | 202 |
inbox | List inbound (2-way) messages | 302 |
Send UTF-8 text as-is. Do not pre-urlencode the message body — the client library already encodes form fields.
| Field | Type | Required | Notes |
|---|---|---|---|
api_key | string | Yes* | Omit if using Authorization or X-Api-Key |
action | enum | Yes | send · balance · history · inbox |
to | string | Send | E.164 without +. Comma-separated for bulk. Alias: destination |
sender | string | No | Max 11 alphanumeric. Default ConectMedia. Alias: source |
message | string | Send | SMS body. Alias: message-box |
schedule | 0 | 1 | No | 1 queues instead of sending immediately |
schedule_datetime | datetime | If scheduled | MySQL datetime, Africa/Nairobi |
limit | integer | No | History/inbox. Default 50. |
offset | integer | No | Skip N records. Default 0. |
start_date / end_date | date | No | YYYY-MM-DD |
Parse JSON, then branch on code. Retry 500s with exponential backoff. Do not retry 100/401 (rotate the key) or 101 (top up balance).
Use country code + national number with no plus, spaces, or dashes. Kenya example: 254712345678. Invalid or empty recipients return 104.
history and inbox accept limit and offset. Page until the messages array is shorter than limit.
The in-browser console is proxied and capped at 10 requests per IP per minute (HTTP 429). Production api.php traffic from your servers is not subject to that console cap.
Retries of send can produce duplicate SMS. Deduplicate on your side before retrying a successful 201, or wait until you have a timeout with no envelope.
Application codes live in the JSON envelope. HTTP status is usually 200.
| Code | Meaning | Action |
|---|---|---|
| 100 | Invalid or missing API key | Auth |
| 101 | Insufficient balance | Send |
| 102 | Rejected (content, account hold, or gateway busy) | Send |
| 103 | No action defined | All |
| 104 | No valid recipients | Send |
| 105 | Message missing | Send |
| 106 | Message contains banned words | Send |
| 200 | Balance retrieved | Balance |
| 201 | Message accepted for delivery | Send |
| 202 | History retrieved | History |
| 302 | Inbox retrieved (including empty) | Inbox |
| 401 | Unauthorized (legacy key error on some paths) | Auth |
| 500 | Server or gateway failure | All |
The following examples demonstrate how to use the ConnectMedia API for various operations.
Comma-separate recipients for bulk. Each successful send deducts credits.
{
"action": "send",
"to": "254712345678,254722000000",
"sender": "ConectMedia",
"message": "Hello from Connect Media"
}
{
"code": "201",
"message": "Message Sent Successfully via Proxy!",
"data": {
"recipients": 2,
"balance": 1249,
"batch_id": 12345
},
"api_version": "8.2.1"
}
{
"code": "101",
"message": "No enough balance to send message"
}
{ "action": "balance" }
{
"code": "200",
"message": "Balance retrieved successfully",
"data": {
"balance": 1250.5,
"currency": "KES"
},
"api_version": "8.2.1"
}
{
"action": "history",
"limit": 10,
"offset": 0,
"start_date": "2026-01-01",
"end_date": "2026-12-31"
}
{
"code": "202",
"message": "Message History Retrieved Successfully",
"data": {
"messages": [
{
"id": 12345,
"message": "Hello from Connect Media!",
"sender": "ConectMedia",
"msisdn": 254712345678,
"status": "PENDING",
"created": "2026-09-14 10:30:45"
}
]
},
"api_version": "8.2.1"
}
https://app.connectmedia.co.ke/api.php
{
"action": "inbox",
"limit": 10
}
{
"code": "302",
"message": "Inbox Data Retrieved Successfully",
"data": {
"messages": [
{
"id": 1,
"sender": "254712345678",
"message": "Yes, please proceed with the order",
"received_at": "2026-09-14 11:45:22"
}
]
},
"api_version": "8.2.1"
}
{
"code": "302",
"message": "No messages found"
}
Install a maintained client library instead of writing HTTP calls yourself.
composer require connectmedia/sms · Packagist · GitHubpip install connectmedia-sms · PyPI · GitHubnpm install connectmedia-sms · npm · GitHubUse environment variables. These snippets talk to production over TLS with Bearer auth.
<?php
class ConnectMediaAPI {
private $apiKey;
private $baseUrl;
public function __construct($apiKey, $baseUrl = 'https://app.connectmedia.co.ke/api.php') {
$this->apiKey = $apiKey;
$this->baseUrl = $baseUrl;
}
public function request(string $action, array $params = []): array {
$ch = curl_init($this->baseUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $this->apiKey,
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_POSTFIELDS => json_encode(array_merge(['action' => $action], $params)),
CURLOPT_TIMEOUT => 30,
]);
$raw = curl_exec($ch);
$err = curl_error($ch);
curl_close($ch);
if ($err) {
return ['ok' => false, 'error' => $err];
}
$json = json_decode($raw, true);
return ['ok' => isset($json['code']) && in_array($json['code'], ['200','201','202','302'], true), 'data' => $json];
}
public function sendSms(string $to, string $message, string $sender = 'ConectMedia'): array {
return $this->request('send', compact('to', 'message', 'sender'));
}
}
$api = new ConnectMediaAPI(getenv('CM_API_KEY'));
print_r($api->sendSms('254712345678', 'Hello from Connect Media'));
export class ConnectMediaAPI {
constructor(apiKey, baseUrl = 'https://app.connectmedia.co.ke/api.php') {
this.apiKey = apiKey;
this.baseUrl = baseUrl;
}
async request(action, params = {}) {
const res = await fetch(this.baseUrl, {
method: 'POST',
headers: {
Authorization: `Bearer ${this.apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ action, ...params }),
});
const data = await res.json();
return { ok: ['200', '201', '202', '302'].includes(String(data.code)), data };
}
sendSms(to, message, sender = 'ConectMedia') {
return this.request('send', { to, message, sender });
}
}
const api = new ConnectMediaAPI(process.env.CM_API_KEY);
const result = await api.sendSms('254712345678', 'Hello from Connect Media');
console.log(result);
import os
import requests
class ConnectMediaAPI:
def __init__(self, api_key, base_url='https://app.connectmedia.co.ke/api.php'):
self.api_key = api_key
self.base_url = base_url
def request(self, action, **params):
r = requests.post(
self.base_url,
headers={'Authorization': f'Bearer {self.api_key}'},
json={'action': action, **params},
timeout=30,
)
data = r.json()
return {'ok': str(data.get('code')) in {'200', '201', '202', '302'}, 'data': data}
def send_sms(self, to, message, sender='ConectMedia'):
return self.request('send', to=to, message=message, sender=sender)
api = ConnectMediaAPI(os.environ['CM_API_KEY'])
print(api.send_sms('254712345678', 'Hello from Connect Media'))
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"os"
)
func main() {
body, _ := json.Marshal(map[string]string{
"action": "send",
"to": "254712345678",
"sender": "ConectMedia",
"message": "Hello from Connect Media",
})
req, _ := http.NewRequest("POST", "https://app.connectmedia.co.ke/api.php", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("CM_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
fmt.Println(res.Status)
}
Runs against production through a same-origin proxy. Use a dedicated test key. 10 requests per IP per minute.