An SMS API lets your website, app or back-office system send SMS to Bangladeshi numbers automatically, without anyone opening a dashboard. With the GotiSMS SMS API you send a JSON request to one endpoint with your API key, and the message goes out on Grameenphone, Robi, Airtel, Banglalink and Teletalk. This tutorial shows working code in cURL, PHP, Node.js and Python.

What do you need before you start?

  1. A GotiSMS account. Sign up here and top up your prepaid wallet.
  2. An API key, created in the dashboard under API keys. Keys start with the prefix shown in the dashboard. In the examples below, replace YOUR_API_KEY with your own.
  3. A sender ID. Every account has a default non-masking sender; you can also request a branded masking sender ID. See masking vs non-masking.
  4. Optional but recommended: add your server's IP addresses to the key's IP allow-list, so the key only works from your servers.

How does the GotiSMS send endpoint work?

The endpoint is POST https://api.gotisms.com/api/messages/send. Every request carries three headers:

  • Authorization: Bearer YOUR_API_KEY
  • Content-Type: application/json
  • Idempotency-Key: <unique value per message> (strongly advised)

The JSON body has four fields:

FieldWhat it holds
recipientsAn array of up to 1,000 Bangladeshi mobile numbers
senderIdYour approved sender ID or default sender
messageThe text of the SMS
categorytransactional or otp

Numbers are accepted as 01712345678, 8801712345678 or +8801712345678.

Promotional messages are not sent through this endpoint. Promotional campaigns go through POST /api/messages/campaign-requests, because their text must be approved by operators, and they can only be sent between 09:00 and 21:00 Bangladesh time.

Why is the Idempotency-Key header important?

Networks fail. Your request may reach GotiSMS but the response may never reach you, and your code retries. Without protection, the customer gets the message twice and you pay twice.

When you send an Idempotency-Key, retrying with the same key never sends or charges twice. Generate a new unique key (for example a UUID) for each new message, store it with your order or OTP record, and reuse it only when retrying that exact request.

Example 1: cURL

Good for testing from a terminal:

curl -X POST https://api.gotisms.com/api/messages/send \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 3f1b8c2e-9a4d-4c51-b7e2-1d6f0a9e5c33" \
  -d '{
    "recipients": ["01712345678"],
    "senderId": "YOUR_SENDER_ID",
    "message": "Your OTP is 4821",
    "category": "transactional"
  }'

Example 2: PHP

Using PHP's built-in cURL extension:

<?php
$apiKey = getenv('GOTISMS_API_KEY'); // keep the key out of source code
$idempotencyKey = bin2hex(random_bytes(16)); // store this to reuse on retry

$payload = [
    'recipients' => ['01712345678'],
    'senderId'   => 'YOUR_SENDER_ID',
    'message'    => 'Your order #1042 has been shipped.',
    'category'   => 'transactional',
];

$ch = curl_init('https://api.gotisms.com/api/messages/send');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 30,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
        'Idempotency-Key: ' . $idempotencyKey,
    ],
    CURLOPT_POSTFIELDS     => json_encode($payload, JSON_UNESCAPED_UNICODE),
]);

$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$error = curl_error($ch);
curl_close($ch);

if ($body === false) {
    // Network error: retry later with the SAME $idempotencyKey
    throw new RuntimeException('Request failed: ' . $error);
}

$result = json_decode($body, true);
if ($status >= 200 && $status < 300) {
    foreach ($result['results'] as $item) {
        // each item has a per-number status
        error_log(json_encode($item));
    }
} else {
    error_log("GotiSMS error $status: $body");
}

JSON_UNESCAPED_UNICODE keeps Bangla text readable in the request body.

Example 3: Node.js

Node.js 18 and later include fetch and crypto.randomUUID():

import { randomUUID } from 'node:crypto';

async function sendSms({ recipients, message, category = 'transactional', idempotencyKey = randomUUID() }) {
  const res = await fetch('https://api.gotisms.com/api/messages/send', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.GOTISMS_API_KEY}`,
      'Content-Type': 'application/json',
      'Idempotency-Key': idempotencyKey,
    },
    body: JSON.stringify({
      recipients,
      senderId: 'YOUR_SENDER_ID',
      message,
      category,
    }),
  });

  const data = await res.json();
  if (!res.ok) {
    throw new Error(`GotiSMS error ${res.status}: ${JSON.stringify(data)}`);
  }
  return data; // contains accepted, rejected and results[]
}

const result = await sendSms({
  recipients: ['01712345678', '+8801812345678'],
  message: 'Your appointment is confirmed for tomorrow at 10:00.',
});
console.log(result.accepted, result.rejected, result.results);

Example 4: Python

Using the popular requests library:

import os
import uuid
import requests

API_URL = "https://api.gotisms.com/api/messages/send"

def send_sms(recipients, message, category="transactional", idempotency_key=None):
    idempotency_key = idempotency_key or str(uuid.uuid4())
    headers = {
        "Authorization": f"Bearer {os.environ['GOTISMS_API_KEY']}",
        "Content-Type": "application/json",
        "Idempotency-Key": idempotency_key,
    }
    payload = {
        "recipients": recipients,
        "senderId": "YOUR_SENDER_ID",
        "message": message,
        "category": category,
    }
    response = requests.post(API_URL, json=payload, headers=headers, timeout=30)
    response.raise_for_status()
    return response.json()

result = send_sms(["8801712345678"], "Your OTP is 4821", category="otp")
for item in result["results"]:
    print(item["status"])

How do you read the response?

A successful response includes accepted, rejected and a results array with a status for each number. Two things to remember:

  • Accepted is not delivered. "Accepted" means the network took the message. Delivery reports arrive later; read them through the API's reports endpoints or on the Campaigns page in the dashboard.
  • Check each number. One request can contain good and bad numbers. Log the per-number status and fix or remove rejected numbers from your list.

Money values in the API are strings in poisha, so "40" means ৳0.40. Avoid floating-point maths on these values; keep them as integers.

How should you handle errors and retries?

  • Timeouts and network errors: retry with exponential backoff, using the same Idempotency-Key.
  • Validation errors (4xx): do not retry blindly. Fix the number, sender ID or message first.
  • Rate limits: the default is 300 requests per minute per key. If you send large volumes, batch up to 1,000 recipients per request instead of one request per number, and queue the rest.
  • Low balance: monitor your wallet and top up before big sends.

Security checklist

  • Store the API key in an environment variable or secret manager, never in Git.
  • Call the API only from your server, never from a browser or mobile app.
  • Turn on the IP allow-list for production keys.
  • Use separate keys for staging and production, and rotate a key if it may have leaked.

For the complete reference, including the reports and campaign-request endpoints, see the SMS API documentation or the SMS API overview.

Frequently asked questions

How many numbers can I send to in one API request?

Up to 1,000 recipients per request. For larger lists, split them into batches.

Which number formats does the API accept?

01712345678, 8801712345678 and +8801712345678 are all accepted. Only Bangladeshi mobile numbers are supported.

Can I send promotional SMS through the send endpoint?

No. The send endpoint takes transactional or otp. Promotional campaigns go through POST /api/messages/campaign-requests for operator approval.

Does GotiSMS support webhooks for delivery reports?

Not at the moment. Delivery reports are read through the API's reports endpoints or the Campaigns page.

Can I send Bangla messages through the API?

Yes. Send the Bangla text as UTF-8 in the message field. It is sent as Unicode SMS, at 70 characters per single SMS.

Ready to build? Create your GotiSMS account, generate an API key, and send your first API message today.