GallantSMS API

One endpoint. You write the message once as a template, your code fills in the blanks and sends it. This page is everything you need to go from nothing to a message on a phone.

Before you start

  1. Create an account. No payment is needed to try the API.
  2. On API Management, create a sandbox key. It is shown once; copy it then. It looks like gs_test_ followed by 32 characters.
  3. On SMS and Templates, write a template. For example: Your login code is {{ code }}. It expires in 5 minutes. It works in the sandbox the moment you save it. Copy its slug from the card.

A sandbox key runs the whole API but stops before the gateway. Nothing is sent, nothing is charged. Each sandbox send shows up on the phone on your API Management page, rendered as a handset would show it, so you can see your variables land. Use it until your integration works, then read Going live.

Send a message

POST https://sms.gallantbyte.com/api/template/send with your key in the x-api-key header and a JSON body naming the template, the number, and a value for each placeholder.

curl -X POST https://sms.gallantbyte.com/api/template/send \
  -H "x-api-key: gs_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"templateSlug":"login_code_a1b2c3d","phoneNumber":"0712345678","variables":{"code":"482913"}}'

Every field is required except variables, which you can leave out for a template with no placeholders. Values are plain strings; a value may not itself contain {{.

The response

A 200 means the gateway accepted the message.

{
  "status": "success",
  "statusCode": 200,
  "message": "SMS sent successfully",
  "mobile": "254712345678",
  "transactionId": "6017967936947375520",
  "template": "login_code_a1b2c3d",
  "segments": 1,
  "characters": 54,
  "encoding": "gsm"
}
FieldWhat it is
transactionIdThe gateway's reference for this message. Keep it; it is what a delivery report will be matched against.
segmentsHow many SMS parts the message was split into. This is what you are charged: one credit per segment.
characters, encodingThe final message length and whether it fit GSM-7 (160 per segment) or needed UCS-2 (70 per segment). One emoji switches the whole message to UCS-2.
mobileThe number as it was sent, in international form.

With a sandbox key the response has the same shape, plus "sandbox": true and a note, and transactionId starts with test_. Your code needs no changes to go live; only the key changes.

Errors

The HTTP status is the outcome. The body always has status: "failed", a short machine-readable code, and a message you can show in a log.

{ "status": "failed", "code": "insufficient_credits", "message": "Insufficient credits" }
StatusCodeMeaningWhat to do
400invalid_request, template_not_found, invalid_template_input, invalid_phone_numberBad input: a missing field, an unknown template slug, a placeholder you did not supply or one the template does not have, a number that does not parse.Fix the request. Do not retry as is.
401unauthorizedMissing, wrong, revoked or expired key.Check the key. Create a new one on API Management if in doubt.
402insufficient_creditsNot enough credit for the segments this message needs. Nothing was sent.Top up on Billing.
402registration_requiredA live key on an account that has not paid the registration yet.Pay it on Billing; it comes back as credit. Sandbox keys are not gated.
403account_suspended, account_banned, forbiddenThe account is suspended, or the key may not do this (free-text sending is not enabled by default).Contact us.
403recipient_opted_outThis person has blocked you, or everyone, at /stop. Nothing was sent or charged.Record it and stop. Do not retry.
409idempotency_conflictA request with the same Idempotency-Key is still running.Wait a moment and send the same request again; you will get its result.
429rate_limitedOver the rate limit.Slow down; see limits.
502gateway_errorThe SMS gateway refused or could not be reached. Nothing was sent, nothing charged.Retry with backoff.
504gateway_timeoutThe gateway did not answer in 15 seconds. Nothing was sent, nothing charged.Safe to retry.

Retries without duplicates

If your request times out you do not know whether the message went. Sending it again could mean two OTPs and two charges. Put a unique value in the Idempotency-Key header (a UUID is fine) and the second request returns the first result instead of sending again.

curl -X POST https://sms.gallantbyte.com/api/template/send \
  -H "x-api-key: gs_test_YOUR_KEY" \
  -H "Idempotency-Key: 6f1c2a9e-4b7d-4e3a-9c1f-2d8e7b6a5f40" \
  -H "Content-Type: application/json" \
  -d '{"templateSlug":"login_code_a1b2c3d","phoneNumber":"0712345678","variables":{"code":"482913"}}'
  • Same key, same account: the original response comes back with the header Idempotent-Replayed: true. Nothing is sent or charged.
  • A key is final once the answer was a 2xx or 4xx. After a 502 or 504 the key is released, so a retry is a real retry.
  • Keys are up to 128 characters of letters, digits, -, _, . or :.

Templates

We send on a transactional sender ID, so the text that goes out has to be reviewed before it is sent live. A template is the reviewed text with blanks in it.

  • Placeholders are written {{ name }}. A name starts with a letter or underscore and contains only letters, digits and underscores. Spaces inside the braces are fine.
  • Every placeholder must be supplied on each send, and you cannot supply one the template does not have. Both are a 400 that names the placeholder.
  • The character count and segments are worked out from the final text, after your values are in. A long name can push a message over 160 characters and into two segments.

Sandbox to live

StatusSandbox keyLive key
Sandbox — every new template starts hereSends400
In review — after you press Submit for productionSends400
Live — after we approve it, usually within a daySendsSends
Changes requested / Rejected — with a note from usSends400

Editing the text of a template that came back from review sends it back for review. Live templates cannot be edited; make a new one.

Acceptable use

GallantSMS is for transactional messages only. Promotional messages will not be sent through it. That is not a preference; it is the condition of the sender ID we send on, and it is why every template is reviewed before it goes live.

Transactional means the recipient is expecting the message because of something they did or hold with you: a one-time code, a receipt, an order or delivery update, a balance, an appointment reminder, a notice about their account. Promotional means anything whose purpose is to sell, upsell, invite, announce an offer, or otherwise market. "Your order has shipped" is transactional; "Your order has shipped, use code SAVE20 on your next one" is not.

What happens if promotional content goes through:

  • A template with promotional content is not approved, with the reason.
  • Sent traffic is scanned. A live message found to be promotional gets a written warning to the account.
  • A second finding suspends the account: every API key is refused until the matter is resolved with us.
  • Repeated or deliberate abuse closes the account. Unused credit is forfeited, because the abuse puts the sender ID every other customer relies on at risk.
  • Where the content breaks the law (the Consumer Protection Act, the Data Protection Act, the CAK's rules on unsolicited messages), we will cooperate with the authorities and say so.

If you are not sure whether a message is transactional, ask us before submitting the template. We answer that question gladly and quickly.

When a recipient says stop

Anyone who receives a message through GallantSMS can go to https://sms.gallantbyte.com/stop, verify their number with a code, and block the sender that messaged them, or every sender on the platform. Per sender is the default: someone who blocks a shop's receipts still gets their bank's codes.

A send to a number that has blocked you is refused with 403 recipient_opted_out before anything is charged. Your code should treat it like an invalid number: record it, stop retrying, and do not try to reach that person another way. We do not tell you who blocked you beyond that response, and the block is theirs to lift, not yours.

You may include the stop address in a template with {{ stop_link }}. We fill it in; you do not supply it. It is optional, and for one-time codes we would leave it out: people are rightly told not to follow links in code messages, and it costs characters. A footer such as Stop: https://sms.gallantbyte.com/stop on receipts or reminders is a reasonable place for it.

Phone numbers

Kenyan numbers in any of the usual forms: 0712345678, +254712345678, 254712345678. We normalise to international form and return it as mobile. A number that does not parse is a 400 before anything is charged.

Rate limits

Per account: 60 requests a minute with live keys, 300 with sandbox keys. Over that you get a 429. If you need more, tell us what you are sending and we will raise it.

Going live

  1. On Billing, pay the registration of KES 300 by M-Pesa. It is not a fee: the whole amount comes back as SMS credit on your account.
  2. Press Submit for production on your template and wait for it to show Live.
  3. Create a live key on API Management and put it where the sandbox key was.

That is the whole change. Messages are charged per segment at the rate on the pricing page, which follows your monthly volume; credits do not expire. Top up from Billing whenever you like.

Delivery reports

A 200 means the gateway accepted the message, not that the phone received it. Delivery reports are being set up now; when they are live, each message's delivery status will be visible on the dashboard and, later, through the API by transactionId. Keep the transactionId from every send so you can match them.

Help

Write to geraldombuthia@gmail.com or use Support in the dashboard, which reaches the same inbox. Include the template slug, the number and the transactionId if you have one. Calls: 0750815413.