Overview
The Eski SMS API is a RESTful interface for sending transactional and campaign SMS, querying delivery status, and managing contacts. All endpoints return JSON.
Base URL
https://eskisms.com/api/v3
Content type
application/json
Charset
UTF-8
Every path below is relative to that base URL. The legacy /api/http prefix serves the same endpoints with the same payloads, so an integration written against the older platform keeps working unchanged.
Authentication
Every request must present an API key. The preferred form is a Bearer token in the Authorization header.
Authorization: Bearer <your_api_token>
For backward compatibility the same key may also be sent as an api_token query parameter, a form field, a JSON body field, or an api_token cookie. The header is checked first, then the query string, cookie, form body, and JSON body.
curl -X POST "https://eskisms.com/api/v3/sms/send" \
-H "Content-Type: application/json" \
-d '{
"api_token": "<your_api_token>",
"recipient": "255717222303",
"message": "Hello",
"sender_id": "YOURID"
}'
Create and revoke keys in the dashboard under API Keys, where each key can be named for the system that uses it. A request with no key, or a revoked one, answers 401 Unauthorized; a key whose account may not send answers 403 Forbidden.
Response envelope
Every response is wrapped in a consistent envelope. The status field is success, error, or info. The data field contains the payload when the request succeeds.
{
"status": "success",
"message": "Message accepted for delivery",
"data": {
"uid": "abc123",
"to": "255717222303",
"from": "YOURID",
"message": "Hello",
"sms_type": "plain",
"status": "queued",
"direction": "api",
"sms_count": 1,
"cost": "1",
"sent_at": "2026-10-16T09:41:12Z"
}
}
{
"status": "error",
"message": "Sender ID \"SHADOW\" is not authorized to send this message."
}
A send that the network refuses is reported as info with HTTP 200: the request was valid, but the message was not accepted for delivery, and the message field carries the reason.
HTTP status codes
| Code | Meaning |
|---|---|
| 200 | Request handled. Check status: success, error (rare on 200) or info. |
| 401 | Missing or invalid API key. |
| 403 | The account or API access is not enabled, or the sender ID is not authorized for it. |
| 404 | The record does not exist, or does not belong to the authenticated account. |
| 422 | Validation failed: missing recipient, message or sender ID, an unparsable datetime, or insufficient balance. |
| 500 | Unexpected server error. Safe to retry the read endpoints. |
/sms/send
Send a single SMS message to one recipient.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| recipient | string | yes | Destination phone number in E.164 or local format. Punctuation is ignored; 9–15 digits are accepted. |
| message | string | yes | Message body. Long messages are split and concatenated automatically. |
| sender_id | string | yes | Approved sender ID for your account. |
| type | string | no | Message type: plain (default) or unicode. otp is accepted and sent as plain. |
Example
curl -X POST https://eskisms.com/api/v3/sms/send \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipient": "255717222303",
"message": "Hello from Eski SMS",
"sender_id": "YOURID",
"type": "plain"
}'
A recipient holding a comma-separated list is treated as a bulk send and answered like /sms/campaign, so one call can address a list. Each number is billed separately; duplicates in the same request are sent once.
/sms/campaign
Send a bulk campaign to multiple recipients.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
| recipient | string | yes | Comma-separated list of phone numbers. |
| message | string | yes | Campaign message body. |
| sender_id | string | yes | Approved sender ID. |
| name | string | no | Campaign name. Generated automatically if omitted. |
| type | string | no | plain (default) or unicode. |
Example
curl -X POST https://eskisms.com/api/v3/sms/campaign \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"recipient": "255717222303,255713000111",
"message": "Campaign launch",
"sender_id": "YOURID",
"name": "Launch Campaign"
}'
{
"status": "success",
"message": "Campaign created successfully",
"data": {
"uid": "campaign123",
"name": "Launch Campaign"
}
}
The whole campaign is checked against your balance before it is accepted, so a list you cannot afford is refused with 422 instead of being created and then skipped.
/sms
List sent messages for the authenticated user. The default window is the last 30 days, 25 rows per page, in the Laravel paginator shape (data, current_page, last_page, total, links).
Query parameters
| Parameter | Description |
|---|---|
| page | Page number (default 1). |
| start_date | Filter from this datetime (Y-m-d H:i:s). |
| end_date | Filter up to this datetime (Y-m-d H:i:s). |
| sms_type | Filter by plain or unicode. |
| direction | Filter by api or outgoing. |
| timezone | IANA name the dates are interpreted and returned in, e.g. Africa/Dar_es_Salaam. Defaults to the platform timezone. |
/sms/:uid
Return one message by its uid, including the SMSC delivery receipt when the network has reported on it. A uid that belongs to another account answers 404.
/delivery-logs
Query delivery logs with the same filters and pagination as /sms. The status parameter narrows the result to one status, for example delivered, rejected or queued.
/delivery-logs/:uid
The delivery detail of one message: its status history and the raw receipt as the network sent it, with the submit and done times.
/campaign/:uid/view
One campaign with its messages, so a bulk send can be followed recipient by recipient.
Message status
A message starts queued, moves through submitted or enroute, and ends delivered — or rejected, undelivered, failed, expired or deleted with the network's error code in the receipt. A status only changes when the network says so: a message with no receipt yet stays queued rather than being reported as delivered.
/contacts
Contact groups and the contacts inside them. Group identifiers are the group uid; the same uid addresses an individual contact.
| Method | Path | Purpose |
|---|---|---|
| GET | /contacts | List contact groups. |
| POST | /contacts | Create a group. |
| POST | /contacts/:uid/show | Read one group. |
| PATCH | /contacts/:uid | Rename a group. |
| DELETE | /contacts/:uid | Delete a group. |
| POST | /contacts/:group_id/all | List the contacts inside a group. |
| POST | /contacts/:group_id/search/:uid | Find one contact in a group. |
| POST | /contacts/:group_id/store | Add a contact to a group. |
| PATCH | /contacts/:group_id/update/:uid | Update a contact in a group. |
| DELETE | /contacts/:group_id/delete/:uid | Remove a contact from a group. |
Example
curl -X POST https://eskisms.com/api/v3/contacts/abc123/store \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "255717222303",
"first_name": "Amina",
"last_name": "Juma"
}'
/me
Return the authenticated account: profile, timezone, locale, and the sender IDs it may use.
/balance
Return the remaining SMS units and the date they expire.
/ping
Confirm the key works and the API is reachable. This is the cheapest endpoint to call from a health check.
Keeping your keys safe
Anyone holding a key can send from your balance, so treat it like a password and never put it in a public repository or a browser-side script. Generate a separate key per system, so one can be revoked without disturbing the others.
A key is shown once when it is created; only a hash is stored and it cannot be displayed again. Lost keys are replaced, not recovered.