Send a message

Queue one or more SMS for delivery.

POST/api/v1/sms/send
Parameters
sender
stringrequired
An approved sender ID, up to 11 characters. Sending with an unapproved ID returns SENDER_NOT_APPROVED.
message
stringrequired
The message body, up to 1600 characters (10 segments). See Segments and pricing. Required unless you send template_id instead.
template_id
string
The id of a saved template, used in place of message. Send exactly one of the two — both, or neither, is a VALIDATION_ERROR.
recipients
string[]required
Up to 1000 numbers. 024…, 233… and +233… are all accepted and normalised; duplicates across formats are removed before you are charged.
type
string
TRANSACTIONAL (default), PROMOTIONAL or OTP. Promotional messages skip recipients who have opted out.
campaign_name
string
Groups this send into a campaign with its own delivery report.
personalization
object
Per-recipient variables, keyed by phone number. Fills {{placeholders}} in the message.
scheduled_for
string
ISO 8601 datetime, at least one minute in the future. Credit is reserved now and released if you cancel.

Request

Shell
curl -X POST https://api.jkasms.com/api/v1/sms/send \
  -H "Authorization: Bearer $JKASMS_SECRET_KEY" \
  -H "X-JKASMS-Org-Id: $JKASMS_ORG_ID" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: invoice-8821-reminder" \
  -d '{
    "sender": "JKASMS",
    "message": "Your appointment is confirmed for tomorrow at 10:00am.",
    "recipients": ["0241234567", "0551234567"]
  }'
Response202
JSON
{
  "success": true,
  "data": {
    "campaign_id": null,
    "accepted": 2,
    "rejected": 0,
    "duplicates_removed": 0,
    "total_segments": 2,
    "total_cost": "0.12",
    "currency": "GHS",
    "messages": [
      {
        "message_id": "cmu9x2k4a0001abcd",
        "recipient": "+233241234567",
        "segments": 1,
        "cost": "0.06",
        "status": "QUEUED"
      }
    ],
    "rejected_recipients": []
  }
}
202, not 200. The message is queued, not delivered. Track it with GET /api/v1/sms/:id or receive a delivery webhook.

Rejected recipients

Invalid numbers are reported in rejected_recipients rather than failing the whole request — one typo in a list of 500 should not stop the other 499. You are not charged for them.

JSON
"rejected_recipients": [
  { "recipient": "0999999999", "reason": "Unknown Ghanaian mobile prefix \"09\"" },
  { "recipient": "+233241234567", "reason": "Recipient has opted out of promotional messages" }
]

Personalization

Substitution happens before segmentation, so you are billed for the rendered text. A long value can push a message into an extra segment.

JSON
{
  "sender": "SCHOOL",
  "message": "Dear {{name}}, fees of GHS {{amount}} are due on {{date}}.",
  "recipients": ["0241234567", "0551234567"],
  "personalization": {
    "+233241234567": { "name": "Ama",   "amount": "450", "date": "30 Sep" },
    "+233551234567": { "name": "Kwame", "amount": "380", "date": "30 Sep" }
  }
}

A placeholder with no matching value renders as an empty string rather than leaving {{name}} visible to the recipient.

Sending from a template

Replace message with template_id to use a saved body. Personalization works identically — the template is rendered per recipient, then segmented, so you are billed for the rendered text.

JSON
{
  "sender": "SCHOOL",
  "template_id": "cmuaxq0of000rtwq8n65tld2n",
  "recipients": ["0241234567"],
  "personalization": {
    "+233241234567": { "name": "Ama", "amount": "450", "date": "30 Sep" }
  }
}
A template_id belonging to another account returns 404 NOT_FOUND, the same as one that does not exist. Templates routinely contain balances and account numbers, so we never confirm that someone else's id is real.

Idempotency

Send an Idempotency-Key on anything you might retry. If your HTTP client times out after we already queued the message, retrying with the same key returns the original result instead of sending twice.

SituationResult
Same key, same body, completedOriginal response replayed. Nothing sent.
Same key, same body, still in flight409 REQUEST_IN_PROGRESS. Retry shortly.
Same key, different body422 DUPLICATE_REQUEST. This is a bug in your code and hiding it would be worse.

Keys are remembered for 24 hours.

Errors

CodeStatusMeaning
SENDER_NOT_APPROVED403Sender ID exists but is not approved yet
INVALID_SENDER400Sender ID is not registered on your account
INSUFFICIENT_BALANCE402Not enough credit for the whole send
INVALID_PHONE_NUMBER400No valid recipients in the request
RECIPIENT_SUPPRESSED400Every recipient has opted out