Base URL
https://api.gotisms.comEvery path below is relative to this address, for example https://api.gotisms.com/api/messages/send. Keep it in your configuration rather than repeated through your code. It is the permanent address and is served over HTTPS only.
Authentication
Every request carries your API key as a bearer token. There is no separate API username or password — the key is the whole credential, so treat it like one.
Authorization: Bearer gs_live_your_key_hereTo get a key, create an account — or sign in and open API management. One key is live per account at a time: regenerating immediately stops the old one working.
401 as an invalid key. That is deliberate — probing should not reveal which of the two it was.Send a message
POST/api/messages/send
Sends immediately. Recipients are normalised and de-duplicated server-side, so the same number twice is one message and one charge.
notice says how many. If there is no non-masking route either, the whole request is refused with a 409 naming the network, rather than half-sent. Promotional messages are never switched. Non-masking (a numeric sender) can be delivered to any network.Headers
| Header | Value | |
|---|---|---|
Authorization | Required | Bearer gs_live_… |
Content-Type | Required | application/json |
Idempotency-Key | Strongly advised | Any string unique to this send |
Body
| Field | Notes | |
|---|---|---|
recipients | Required | Array of up to 1,000 numbers. 01712345678, 8801712345678 and +8801712345678 all work. |
senderId | Required | An approved sender ID for your account. |
message | Required | Up to 1000 characters. |
category | Required | transactional or otp. Promotional is submitted for approval — see below. |
title | Optional | What to call this send in your reports. |
curl -X POST https://api.gotisms.com/api/messages/send \
-H "Authorization: Bearer gs_live_your_key_here" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-48213-otp" \
-d '{
"title": "Order 48213 OTP",
"recipients": ["01712345678"],
"senderId": "NON-MASKING",
"message": "Your OTP is 4821",
"category": "transactional"
}'Response
{
"batchId": "1df687f7-b686-4624-8435-30922c93dd4b",
"title": "Order 48213 OTP",
"accepted": 1,
"rejected": 0,
"chargedMinor": "40",
"reservedMinor": "40",
"supplier": "SSLWIRELESS",
"results": [
{
"msisdn": "8801712345678",
"operator": "grameenphone",
"status": "accepted",
"supplierStatus": "SUCCESS",
"chargedMinor": "40",
"detail": null
}
],
"notice": null
}"40" is ৳0.40. Parse it as an integer; binary floating point cannot represent money exactly.Promotional messages
POST/api/messages/campaign-requests
Promotional SMS cannot be sent directly. Each promotional campaign needs an ID that the operator approves for the exact message text, so it is submitted for approval: we create the campaign with the operator and send it once it is approved. Posting category: "promotional" to the send endpoint returns 409.
{
"title": "Eid offer",
"recipients": ["01712345678", "01812345678"],
"senderId": "ACMETRADE",
"message": "Eid Mubarak! 20% off all orders this week."
}It is checked like a send — sender, wording, numbers and networks — so a request that could never go is refused straight away. The response has a status of pending.
GET /api/messages/campaign-requests lists yours with their status; DELETE /api/messages/campaign-requests/{id} cancels one that is still pending.
Delivery status
A message moves from accepted to delivered, undelivered or rejected when the network reports back. Read it with the reports endpoints below.
unknown — which means exactly that: it was accepted, and the network never said what happened next. Do not treat unknown as failed and resend automatically, or a recipient may get the same message twice.Schedule a send
POST/api/messages/scheduled
The same body as an immediate send, plus scheduledAt as an ISO instant. At least a minute ahead, at most 90 days.
{
"title": "Eid campaign",
"recipients": ["01712345678"],
"senderId": "NON-MASKING",
"message": "Eid Mubarak from Acme",
"category": "promotional",
"scheduledAt": "2026-09-20T04:00:00Z"
}GET /api/messages/scheduled lists them; DELETE /api/messages/scheduled/{id} cancels one that has not run.
Balance
GET/api/wallet
{
"customerId": "46d42cb8-c2f8-419e-83a4-be5e13b3e0ca",
"availableMinor": "49920",
"reservedMinor": "0",
"totalMinor": "49920",
"creditLimitMinor": null,
"lowBalanceThresholdMinor": "10000",
"isLow": false
}availableMinor is what you can spend right now. reservedMinor is held against sends in flight.
Rates
GET/api/pricing
What you pay per segment, by category and sender kind. A masked (branded) sender is priced separately from a numeric one.
{
"rates": [
{ "category": "otp", "masked": false, "label": "OTP non-masking", "rateMinor": "50" },
{ "category": "transactional", "masked": false, "label": "Transactional non-masking", "rateMinor": "40" },
{ "category": "promotional", "masked": true, "label": "Promotional masking", "rateMinor": "25" }
]
}To price a message before sending it, post to /api/pricing/preview with message, category, senderId and optionally msisdns. It runs the same calculation the send does, so the two can never disagree.
Sender IDs
GET/api/sender-ids/usable
The senders you may currently send with. Use value in a send.
{
"values": [
{ "value": "NON-MASKING", "label": "Default Non-Masking", "masked": false },
{ "value": "GOTITEST", "label": "GOTITEST", "masked": true }
]
}Reports
Both take from and to as YYYY-MM-DD.
GET/api/reports/campaigns?from=2026-09-01&to=2026-09-11
One row per send, with delivered, failed and pending counts. Add &category=promotional to narrow it.
GET/api/reports/campaigns/{batchId}/failures
The numbers a send did not reach, each with the operator's own reason.
GET/api/reports/transactional?from=2026-09-01&to=2026-09-11
Every non-promotional message, newest first. Pass limit (up to 200, default 50); when the response has a nextCursor, request again with &cursor= set to it for the next page. A date range covers at most 92 days.
Errors
Errors are JSON and the status code carries the meaning.
| Status | Means | What to do |
|---|---|---|
400 | The body failed validation | Read fields — it names each problem |
401 | Bad key, revoked key, or a blocked IP | Check the key and your IP whitelist |
403 | Not allowed for this account | e.g. masked sending is not enabled |
404 | No such record | Check the id |
409 | Refused, not broken | e.g. no rate configured, or a duplicate |
429 | Too many requests | Wait for X-RateLimit-Reset seconds, then retry |
{
"statusCode": 400,
"error": "Validation failed",
"message": "Please correct the highlighted fields",
"fields": {
"recipients": "Add at least one recipient",
"senderId": "Choose a sender ID"
}
}Rate limit
300 requests per minute per IP address. Every response carries X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (seconds until the window resets), so a client can slow down before it is refused.
409 is the API refusing on purpose — an unpriced route, for instance, is rejected rather than guessed at, because a wrong charge cannot be undone once a message has gone.