Developer documentation
ইউনিয়ন সনদপত্র যাচাই
Sending SMS
Send one SMS, the same SMS to many numbers, a different SMS to each number, or an SMS to a saved group. Every send endpoint validates the whole request, charges your wallet once for the total, and returns 202 Accepted with the batch and message IDs you use for tracking.
Send modes
| Mode | Endpoint | Batch type |
|---|---|---|
| One number, one body | POST /bulk-sms/send with exactly one number in to, as a string or a one-item array | single |
| Many numbers, same body | POST /bulk-sms/send with to as an array of 2 to 10,000 numbers | broadcast |
| Many numbers, own body each | POST /bulk-sms/send-many with messages, up to 10,000 items of {to, body} | personalized |
| A saved group | POST /bulk-sms/groups/{group_id}/send | group |
Recipients
- Accepted formats:
01XXXXXXXXX,8801XXXXXXXXXand+8801XXXXXXXXX, with an operator prefix from013to019. Numbers are normalized to8801XXXXXXXXXin responses. - Duplicates within one broadcast or group send are removed, so each number is sent and charged once.
send-manykeeps a repeated number only when its bodies differ. - Any invalid number rejects the whole request with
422 validation_error, one entry per bad number under its input path (to.4,messages.2.to). The API never partially accepts a request, so a charge is never a surprise. - More than 10,000 recipients in one request returns 422 under
toormessages. Group sends have no 10,000-recipient cap: large groups are sent in background chunks.
Sender ID
- Omit
fromto send from the default non-masking sender at the non-masking rate. - Set
fromto one of your approved masking names to send with your brand name at the masking rate. Any other value returns 422 underfrom. Group sends honourfromtoo. - Call
GET /bulk-sms/sendersto list every sender you can use, with the price per part your account pays for it.
Body
The SMS text goes in body. It can be up to 10 parts long; see Encoding, parts and pricing for how parts are counted. A longer body returns 422 under body (or messages.N.body) stating the actual part count.
Every body is checked automatically for harmful content before anything is charged. A rejected body returns 422 content_rejected and nothing is charged or queued. The response never says which words matched. If a normal, harmless message is rejected, contact support.
What happens after 202
- The total cost is debited from your wallet once, when the request is accepted. If the wallet cannot cover it you get
402 insufficient_balanceand nothing is charged or queued. - Every message starts as
queued. Messages are sent in the background in chunks and move tosending, thensentorfailed. - Every failed message is refunded to your wallet exactly once.
- When the batch has 1,000 recipients or fewer, the response embeds
messages. Above 1,000,messagesis omitted andmessages_urlpoints to/bulk-sms/batches/{batch_id}/messages.
Endpoints
bulk-sms:read
List the sender IDs you can put in from. The default sender comes first with is_default true, followed by your approved masking names. rate_per_part is the price your account pays for one SMS part from that sender.
curl -X GET https://e-amarseba.com/api/v1/bulk-sms/senders \
-H "Authorization: Bearer $AMARSEBA_API_KEY" \
-H "Accept: application/json"
use Illuminate\Support\Facades\Http;
$response = Http::withToken(config('services.amarseba.key'))
->acceptJson()
->get('https://e-amarseba.com/api/v1/bulk-sms/senders');
$result = $response->json();
const response = await fetch("https://e-amarseba.com/api/v1/bulk-sms/senders", {
method: "GET",
headers: {
Authorization: `Bearer ${process.env.AMARSEBA_API_KEY}`,
Accept: "application/json",
},
});
const result = await response.json();
import os
import requests
response = requests.get(
"https://e-amarseba.com/api/v1/bulk-sms/senders",
headers={
"Authorization": f"Bearer {os.environ['AMARSEBA_API_KEY']}",
"Accept": "application/json",
},
)
result = response.json()
{
"success": true,
"message": "Sender IDs retrieved",
"data": [
{
"sender": "Amarseba",
"is_masking": false,
"is_default": true,
"rate_per_part": "0.30",
"currency": "BDT"
},
{
"sender": "MyShop",
"is_masking": true,
"is_default": false,
"rate_per_part": "0.50",
"currency": "BDT"
}
],
"code": 200,
"timestamp": "2026-09-26T10:00:00.000000Z"
}
bulk-sms:send
Send one body to one number or to a list of numbers.
| Name | Type | Required | Description |
|---|---|---|---|
to |
string or string[] | Yes | One number, or an array of up to 10,000 numbers. |
body |
string | Yes | The SMS text, up to 10 parts. |
from |
string | No | An approved masking name. Omit for the default sender. |
curl -X POST https://e-amarseba.com/api/v1/bulk-sms/send \
-H "Authorization: Bearer $AMARSEBA_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"to": [
"01712345678",
"01812345678"
],
"body": "Your OTP is 4821. It expires in 5 minutes."
}'
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
$response = Http::withToken(config('services.amarseba.key'))
->acceptJson()
->withHeaders(['Idempotency-Key' => (string) Str::uuid()])
->post('https://e-amarseba.com/api/v1/bulk-sms/send', [
'to' => [
'01712345678',
'01812345678',
],
'body' => 'Your OTP is 4821. It expires in 5 minutes.',
]);
$result = $response->json();
const response = await fetch("https://e-amarseba.com/api/v1/bulk-sms/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AMARSEBA_API_KEY}`,
Accept: "application/json",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"to": [
"01712345678",
"01812345678"
],
"body": "Your OTP is 4821. It expires in 5 minutes."
}),
});
const result = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://e-amarseba.com/api/v1/bulk-sms/send",
headers={
"Authorization": f"Bearer {os.environ['AMARSEBA_API_KEY']}",
"Accept": "application/json",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"to": [
"01712345678",
"01812345678",
],
"body": "Your OTP is 4821. It expires in 5 minutes.",
},
)
result = response.json()
{
"success": true,
"message": "SMS queued for delivery",
"data": {
"batch_id": "bat_01j8z3k6q9w2m4n7p5r8t1v3x6",
"type": "broadcast",
"status": "queued",
"mode": "live",
"sender": "Amarseba",
"is_masking": false,
"recipient_count": 2,
"total_parts": 2,
"total_cost": "0.60",
"balance_after": "99.40",
"messages": [
{
"message_id": "msg_01j8z3k6qa4b8c2d6e0f3g7h1j",
"to": "8801712345678",
"encoding": "gsm7",
"parts": 1,
"cost": "0.30",
"status": "queued"
},
{
"message_id": "msg_01j8z3k6qb5c9d3e7f1g4h8j2k",
"to": "8801812345678",
"encoding": "gsm7",
"parts": 1,
"cost": "0.30",
"status": "queued"
}
]
},
"code": 202,
"timestamp": "2026-09-26T10:00:00.000000Z"
}
{
"success": false,
"message": "Your wallet balance is too low for this request",
"data": {
"required": "0.60",
"available": "0.20",
"shortfall": "0.40",
"currency": "BDT"
},
"errors": {
"balance": [
"This request costs 0.60 BDT but your wallet balance is 0.20 BDT"
]
},
"code": 402,
"error_code": "insufficient_balance",
"timestamp": "2026-09-26T10:00:00.000000Z"
}
bulk-sms:send
Send a different body to each number in one request.
| Name | Type | Required | Description |
|---|---|---|---|
messages |
object[] | Yes | Up to 10,000 items. |
messages.*.to |
string | Yes | The recipient number. |
messages.*.body |
string | Yes | The SMS text for this recipient, up to 10 parts. |
from |
string | No | An approved masking name, used for every message. |
curl -X POST https://e-amarseba.com/api/v1/bulk-sms/send-many \
-H "Authorization: Bearer $AMARSEBA_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"messages": [
{
"to": "01712345678",
"body": "Hi Rahim, your order #1042 has shipped."
},
{
"to": "01812345678",
"body": "Hi Karim, your order #1043 has shipped."
}
]
}'
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
$response = Http::withToken(config('services.amarseba.key'))
->acceptJson()
->withHeaders(['Idempotency-Key' => (string) Str::uuid()])
->post('https://e-amarseba.com/api/v1/bulk-sms/send-many', [
'messages' => [
[
'to' => '01712345678',
'body' => 'Hi Rahim, your order #1042 has shipped.',
],
[
'to' => '01812345678',
'body' => 'Hi Karim, your order #1043 has shipped.',
],
],
]);
$result = $response->json();
const response = await fetch("https://e-amarseba.com/api/v1/bulk-sms/send-many", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AMARSEBA_API_KEY}`,
Accept: "application/json",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"messages": [
{
"to": "01712345678",
"body": "Hi Rahim, your order #1042 has shipped."
},
{
"to": "01812345678",
"body": "Hi Karim, your order #1043 has shipped."
}
]
}),
});
const result = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://e-amarseba.com/api/v1/bulk-sms/send-many",
headers={
"Authorization": f"Bearer {os.environ['AMARSEBA_API_KEY']}",
"Accept": "application/json",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"messages": [
{
"to": "01712345678",
"body": "Hi Rahim, your order #1042 has shipped.",
},
{
"to": "01812345678",
"body": "Hi Karim, your order #1043 has shipped.",
},
],
},
)
result = response.json()
{
"success": false,
"message": "Recipient #2 is not a valid Bangladeshi mobile number",
"data": null,
"errors": {
"messages.1.to": [
"Recipient #2 is not a valid Bangladeshi mobile number"
]
},
"code": 422,
"error_code": "validation_error",
"timestamp": "2026-09-26T10:00:00.000000Z"
}
bulk-sms:send
Send one body to every active contact in a group. An inactive group returns 422 under group_id with "The group is not active", and a group without active contacts returns "The group has no active contacts".
| Name | Type | Required | Description |
|---|---|---|---|
group_id |
path | Yes | The group ID, for example grp_01j8…. |
body |
string | Yes | The SMS text, up to 10 parts. |
from |
string | No | An approved masking name. Omit for the default sender. |
curl -g -X POST https://e-amarseba.com/api/v1/bulk-sms/groups/{group_id}/send \
-H "Authorization: Bearer $AMARSEBA_API_KEY" \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"body": "Your OTP is 4821. It expires in 5 minutes."
}'
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
$response = Http::withToken(config('services.amarseba.key'))
->acceptJson()
->withHeaders(['Idempotency-Key' => (string) Str::uuid()])
->post('https://e-amarseba.com/api/v1/bulk-sms/groups/{group_id}/send', [
'body' => 'Your OTP is 4821. It expires in 5 minutes.',
]);
$result = $response->json();
const response = await fetch("https://e-amarseba.com/api/v1/bulk-sms/groups/{group_id}/send", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AMARSEBA_API_KEY}`,
Accept: "application/json",
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
"body": "Your OTP is 4821. It expires in 5 minutes."
}),
});
const result = await response.json();
import os
import uuid
import requests
response = requests.post(
"https://e-amarseba.com/api/v1/bulk-sms/groups/{group_id}/send",
headers={
"Authorization": f"Bearer {os.environ['AMARSEBA_API_KEY']}",
"Accept": "application/json",
"Idempotency-Key": str(uuid.uuid4()),
},
json={
"body": "Your OTP is 4821. It expires in 5 minutes.",
},
)
result = response.json()
{
"success": false,
"message": "The group is not active",
"data": null,
"errors": {
"group_id": [
"The group is not active"
]
},
"code": 422,
"error_code": "validation_error",
"timestamp": "2026-09-26T10:00:00.000000Z"
}