Skip to content
BitSMS
Menu

Developers / API reference

BitSMS Public API v1.0.0

BitSMS is an SMS gateway operated by BitCommerz. One API, multiple upstream providers; BitSMS chooses the route, retries, fails over and reports delivery.

Authentication

ScopeAllows
messages:sendPOST /messages, POST /messages/bulk
messages:readGET /messages, GET /messages/{id}
otpPOST /otp, POST /otp/{otp_id}/verify
balance:readGET /balance
senders:readGET /sender-ids
webhooks:write/webhooks*

bsk_test_… keys use the sandbox: nothing is delivered or charged, and the sandbox behaves deterministically by the last two digits of the number (see the developer guide).

Asynchronous sending

Idempotency

Rate limits

Numbers

Message parts and price

Money

Errors

Stability

Base URL: https://api.[DOMAIN]/v1 — Production

Base URL: http://localhost:8080/v1 — Local development (docker-compose)

Download the OpenAPI 3.1 file to generate a client or import into Postman.

Messages

get/messages

List messages

Newest first, cursor-paginated. Requires messages:read. Searches within the last 13 months.

Parameters

NameInTypeDescription
statusqueryMessageStatus
toquerystring

Destination in any accepted form.

client_refquerystring
batch_idquerystring (uuid)
sender_idquerystring
created_fromquerystring (date-time)
created_toquerystring (date-time)
limitqueryinteger
cursorquerystring

Responses

  • 200A page of messages. — object
  • 400Malformed request (invalid JSON, missing field, bad type). — Error
  • 401Missing, invalid, revoked or expired API key. — Error
  • 403Key lacks a scope, IP not allowed, account not verified / suspended / closed, or sender not allowed. — Error
  • 429Rate limit exceeded. — Error

post/messages

Send a single SMS

Validates, prices and debits the message, then queues it. Requires scope messages:send. The default class is transactional.

Parameters

NameInTypeDescription
Idempotency-Key*headerstring

Unique per logical send. Same key + same body within 7 days returns the original result.

Request body

application/json: SendMessageRequest

Responses

  • 202Accepted and queued. — Message
  • 400Malformed request (invalid JSON, missing field, bad type). — Error
  • 401Missing, invalid, revoked or expired API key. — Error
  • 402Balance does not cover the cost (`insufficient_credit`). — Error
  • 403Key lacks a scope, IP not allowed, account not verified / suspended / closed, or sender not allowed. — Error
  • 409Idempotency key reused with a different body, or the same key is in flight. — Error
  • 422Semantically invalid (bad number, text too long, unknown sender). — Error
  • 429Rate limit exceeded. — Error
  • 503Temporarily unable to accept (`backpressure`, `internal_unavailable`). Nothing was charged. — Error

post/messages/bulk

Send one text to many numbers

Up to 10 000 recipients per request. Valid recipients are accepted and debited in one transaction; invalid ones are listed in rejected and not charged. If the balance cannot cover all valid recipients the whole request fails with 402 insufficient_credit (nothing is sent). Default class bulk. May return 503 backpressure when the bulk queue is saturated; retry after Retry-After.

Parameters

NameInTypeDescription
Idempotency-Key*headerstring

Unique per logical send. Same key + same body within 7 days returns the original result.

Request body

application/json: SendBulkRequest

Responses

  • 202Batch accepted. — BulkResult
  • 400Malformed request (invalid JSON, missing field, bad type). — Error
  • 401Missing, invalid, revoked or expired API key. — Error
  • 402Balance does not cover the cost (`insufficient_credit`). — Error
  • 403Key lacks a scope, IP not allowed, account not verified / suspended / closed, or sender not allowed. — Error
  • 409Idempotency key reused with a different body, or the same key is in flight. — Error
  • 422Semantically invalid (bad number, text too long, unknown sender). — Error
  • 429Rate limit exceeded. — Error
  • 503Temporarily unable to accept (`backpressure`, `internal_unavailable`). Nothing was charged. — Error

get/messages/{id}

Get a message and its delivery status

Requires messages:read. ?include=events adds the status history.

Parameters

NameInTypeDescription
id*pathstring (uuid)
includequerystring

Responses

  • 200The message. — Message
  • 401Missing, invalid, revoked or expired API key. — Error
  • 403Key lacks a scope, IP not allowed, account not verified / suspended / closed, or sender not allowed. — Error
  • 404Not found (or belongs to another account). — Error
  • 429Rate limit exceeded. — Error

OTP

post/otp

Generate and send a one-time password

BitSMS generates the code, sends it with class otp (highest priority), stores only a hash, and returns an otp_id to verify against. The code is never returned. Requires scope otp.

The template must contain {{code}} once; default "Your {{app_name}} code is {{code}}. Valid for {{ttl_minutes}} minutes.".

Limits: 3 OTPs per number per 10 minutes, 10 per number per day (per tenant).

Parameters

NameInTypeDescription
Idempotency-Key*headerstring

Unique per logical send. Same key + same body within 7 days returns the original result.

Request body

application/json: SendOtpRequest

Responses

  • 202OTP sent (queued). — Otp
  • 400Malformed request (invalid JSON, missing field, bad type). — Error
  • 401Missing, invalid, revoked or expired API key. — Error
  • 402Balance does not cover the cost (`insufficient_credit`). — Error
  • 403Key lacks a scope, IP not allowed, account not verified / suspended / closed, or sender not allowed. — Error
  • 409Idempotency key reused with a different body, or the same key is in flight. — Error
  • 422Semantically invalid (bad number, text too long, unknown sender). — Error
  • 429Rate limit exceeded. — Error

post/otp/{otp_id}/verify

Verify a code

Returns 200 {"verified": true} on success. A wrong code returns 200 with verified: false and attempts_remaining. After 5 wrong codes, or after expiry, returns 410 with otp_exhausted / otp_expired. A verified OTP cannot be verified again (410 otp_already_verified).

Parameters

NameInTypeDescription
otp_id*pathstring (uuid)

Request body

application/json: object

FieldTypeDescription
code*string

Responses

  • 200Verification result. — OtpVerifyResult
  • 401Missing, invalid, revoked or expired API key. — Error
  • 403Key lacks a scope, IP not allowed, account not verified / suspended / closed, or sender not allowed. — Error
  • 404Not found (or belongs to another account). — Error
  • 410OTP expired, exhausted, or already verified. — Error
  • 429Rate limit exceeded. — Error

Account

get/balance

Current credit balance and prices

Requires balance:read.

Responses

  • 200Balance. — Balance
  • 401Missing, invalid, revoked or expired API key. — Error
  • 403Key lacks a scope, IP not allowed, account not verified / suspended / closed, or sender not allowed. — Error
  • 429Rate limit exceeded. — Error

get/sender-ids

Sender IDs this account may use

Approved masking sender IDs and the default non-masking sender. Requires senders:read. Requests for new masking IDs are made in the dashboard (documents required).

Responses

  • 200Sender IDs. — object
  • 401Missing, invalid, revoked or expired API key. — Error
  • 403Key lacks a scope, IP not allowed, account not verified / suspended / closed, or sender not allowed. — Error

Webhooks

get/webhooks

List webhook endpoints

Responses

  • 200Endpoints. — object
  • 401Missing, invalid, revoked or expired API key. — Error
  • 403Key lacks a scope, IP not allowed, account not verified / suspended / closed, or sender not allowed. — Error

post/webhooks

Register a delivery-report webhook

Max 5 endpoints per account. URL must be https:// and publicly resolvable (private/loopback addresses are refused). The signing secret is returned once in this response.

Request body

application/json: WebhookCreate

Responses

  • 201Created. — WebhookWithSecret
  • 400Malformed request (invalid JSON, missing field, bad type). — Error
  • 401Missing, invalid, revoked or expired API key. — Error
  • 403Key lacks a scope, IP not allowed, account not verified / suspended / closed, or sender not allowed. — Error
  • 422Semantically invalid (bad number, text too long, unknown sender). — Error

get/webhooks/{webhook_id}

Get a webhook endpoint

Parameters

NameInTypeDescription
webhook_id*pathstring (uuid)

Responses

  • 200Endpoint. — Webhook
  • 401Missing, invalid, revoked or expired API key. — Error
  • 404Not found (or belongs to another account). — Error

patch/webhooks/{webhook_id}

Update URL, events, or re-enable

Parameters

NameInTypeDescription
webhook_id*pathstring (uuid)

Request body

application/json: WebhookUpdate

Responses

  • 200Updated. — Webhook
  • 400Malformed request (invalid JSON, missing field, bad type). — Error
  • 401Missing, invalid, revoked or expired API key. — Error
  • 404Not found (or belongs to another account). — Error
  • 422Semantically invalid (bad number, text too long, unknown sender). — Error

delete/webhooks/{webhook_id}

Delete an endpoint

Parameters

NameInTypeDescription
webhook_id*pathstring (uuid)

Responses

  • 204Deleted.
  • 401Missing, invalid, revoked or expired API key. — Error
  • 404Not found (or belongs to another account). — Error

post/webhooks/{webhook_id}/rotate-secret

Rotate the signing secret

The old secret stays valid for 24 hours; during that time deliveries carry two signatures.

Parameters

NameInTypeDescription
webhook_id*pathstring (uuid)

Responses

  • 200New secret (shown once). — WebhookWithSecret
  • 401Missing, invalid, revoked or expired API key. — Error
  • 404Not found (or belongs to another account). — Error

post/webhooks/{webhook_id}/test

Send a test event now

Sends a signed sample message.delivered event (event_id test_<uuid>, message_id all zeros) to the endpoint right away and returns the result. Nothing is stored or retried.

Parameters

NameInTypeDescription
webhook_id*pathstring (uuid)

Responses

  • 200Result of the test delivery. — WebhookDeliveryAttempt
  • 401Missing, invalid, revoked or expired API key. — Error
  • 404Not found (or belongs to another account). — Error
  • 429Rate limit exceeded. — Error

post/webhooks/{webhook_id}/replay

Re-deliver events from a time range

Queues re-delivery of events created in [from, to) (max 72 hours, within the last 30 days). Events keep their original event_id.

Parameters

NameInTypeDescription
webhook_id*pathstring (uuid)

Request body

application/json: object

FieldTypeDescription
from*string (date-time)
to*string (date-time)
only_failedboolean

default true

Responses

  • 202Replay queued. — object
  • 400Malformed request (invalid JSON, missing field, bad type). — Error
  • 401Missing, invalid, revoked or expired API key. — Error
  • 404Not found (or belongs to another account). — Error

Tools

post/tools/sms-count

Count characters and parts for a text

Same counting the send endpoints use. No authentication required; rate-limited per IP (60/minute). Texts over 10 parts are counted, not rejected; the send endpoints reject them with text_too_long.

No authentication.

Request body

application/json: object

FieldTypeDescription
text*string

max length 1530

Responses

  • 200Count. — SmsCount
  • 400Malformed request (invalid JSON, missing field, bad type). — Error
  • 429Rate limit exceeded. — Error

Schemas

Money

FieldTypeDescription
amount*integer (int64)

Minor units (paisa).

currency*string

MessageClass

Priority class. otp > transactional > bulk. Affects queue priority and retry policy only, not price.

One of: otptransactionalbulk

MessageStatus

Public view of the internal state machine: internal accepted/queued → queued; processing/retry_wait → sending. queued → sending → sent → delivered, or a terminal failure. Terminal: delivered, failed, failed_unknown, expired, rejected. Extensible: clients must handle unknown values as non-terminal.

One of: queuedsendingsentdeliveredfailedfailed_unknownexpiredrejected

SendMessageRequest

FieldTypeDescription
to*string

Bangladesh mobile number: 01XXXXXXXXX, 8801XXXXXXXXX or +8801XXXXXXXXX.

text*string

Up to 10 parts (1530 GSM-7 chars / 670 Unicode).

max length 1530

sender_idstring

Approved masking sender name. Omit for the default non-masking sender.

max length 11

classMessageClass
client_refstring

Your reference; searchable and echoed in webhooks.

max length 128

validity_secondsinteger

Give up if not handed to a provider within this time.

default 3600 · min 60 · max 86400

metadataobject

Echoed in webhooks. Max 2 KB serialized.

SendBulkRequest

FieldTypeDescription
to*array of string

Duplicates are sent once.

max 10000 items

text*string

max length 1530

sender_idstring

max length 11

classMessageClass
client_refstring

max length 128

validity_secondsinteger

default 3600 · min 60 · max 86400

Message

FieldTypeDescription
id*string (uuid)
batch_idstring | null (uuid)
to*string
sender_id*string
sender_type*string

one of: "masking", "non_masking"

class*MessageClass
textstring

Present on GET only.

status*MessageStatus
status_reasonstring | null

Stable reason code for failures, e.g. all_providers_failed, invalid_number, no_dlr.

encoding*string

one of: "gsm7", "ucs2"

charactersinteger
parts*integer

min 1 · max 10

cost*Money
refundedboolean
client_refstring | null
metadataobject | null
providerstring | null

Display name of the provider that carried the message.

created_at*string (date-time)
sent_atstring | null (date-time)
delivered_atstring | null (date-time)
expires_atstring (date-time)
eventsarray of object

Only with include=events.

BulkResult

FieldTypeDescription
batch_id*string (uuid)
parts_per_messageinteger
encodingstring

one of: "gsm7", "ucs2"

total_cost*Money
accepted*array of object
rejected*array of object

SendOtpRequest

FieldTypeDescription
to*string
lengthinteger

default 6 · min 4 · max 8

ttl_secondsinteger

default 300 · min 60 · max 600

templatestring

Must contain {{code}}. May use {{app_name}}, {{ttl_minutes}}.

max length 300

app_namestring

max length 30

sender_idstring

max length 11

client_refstring

max length 128

Otp

FieldTypeDescription
otp_id*string (uuid)
message_id*string (uuid)
to*string
expires_at*string (date-time)
costMoney

OtpVerifyResult

FieldTypeDescription
verified*boolean
attempts_remaininginteger

Balance

FieldTypeDescription
balance*Money
low_balance_threshold*Money
account_statusstring

one of: "trial", "active", "suspended", "closed"

prices*object
estimated_parts_remainingobject

SenderId

FieldTypeDescription
name*string
type*string

one of: "masking", "non_masking"

is_default*boolean
approved_atstring | null (date-time)

WebhookEventType

One of: message.sentmessage.deliveredmessage.failedmessage.expiredmessage.rejectedotp.verified

WebhookCreate

FieldTypeDescription
url*string (uri)
events*array of WebhookEventType
descriptionstring

max length 100

WebhookUpdate

FieldTypeDescription
urlstring (uri)
eventsarray of WebhookEventType
descriptionstring

max length 100

enabledboolean

Webhook

FieldTypeDescription
id*string (uuid)
url*string
events*array of WebhookEventType
descriptionstring | null
status*string

one of: "active", "disabled_by_user", "disabled_failing"

secret_prefix*string
consecutive_failuresinteger
created_at*string (date-time)

WebhookWithSecret

Includes Webhook

FieldTypeDescription
secret*string

Shown once.

WebhookDeliveryAttempt

FieldTypeDescription
delivered*boolean
http_statusinteger | null
duration_msinteger
errorstring | null

WebhookEvent

FieldTypeDescription
event_id*string

Stable: <message_id>:<status>. Dedupe on it.

type*WebhookEventType
created_at*string (date-time)
data*object

SmsCount

FieldTypeDescription
encoding*string

one of: "gsm7", "ucs2"

characters*integer

GSM-7 septets (extension chars count 2) or UTF-16 code units.

parts*integer
per_part*integer

one of: 160, 153, 70, 67

remaining_in_part*integer
non_gsm_charactersarray of string

First characters that forced Unicode (max 10).

ErrorCode

Stable machine-readable codes. Extensible — new codes may be added.

CodeHTTPMeaning
invalid_request400Malformed JSON / missing or wrong-typed field (details lists fields)
idempotency_key_missing400Send endpoint called without Idempotency-Key
unauthorized401Missing or invalid API key
api_key_revoked401Key revoked or expired
insufficient_credit402Balance below cost (details.required, details.balance)
scope_missing403Key lacks the scope (details.required_scope)
ip_not_allowed403Caller IP not in key allowlist
account_not_verified403Sign-up not completed
account_suspended403Account suspended
account_closed403Account closed
sender_not_allowed403Sender ID unknown, not approved, or disabled for this account
not_found404Resource not found
idempotency_key_reused409Same key, different body
idempotency_in_progress409Same key still being processed; retry shortly
otp_expired410OTP expired
otp_exhausted410Too many wrong codes
otp_already_verified410Already verified
invalid_destination422Not a valid Bangladesh mobile number
recipient_opted_out422Number opted out of your bulk messages
text_too_long422More than 10 parts
text_empty422Empty text
invalid_template422OTP template missing {{code}}
too_many_recipients422Bulk over 10 000
webhook_url_invalid422Not https or resolves to a private address
webhook_limit_reached422Max 5 endpoints
rate_limited429Key rate limit exceeded
otp_rate_limited429Too many OTPs to this number
backpressure503Bulk queue saturated; retry after Retry-After
internal_unavailable503Temporary internal failure; nothing charged
internal_error500Unexpected error; nothing charged unless a message id was returned

One of: invalid_requestidempotency_key_missingunauthorizedapi_key_revokedinsufficient_creditscope_missingip_not_allowedaccount_not_verifiedaccount_suspendedaccount_closedsender_not_allowednot_foundidempotency_key_reusedidempotency_in_progressotp_expiredotp_exhaustedotp_already_verifiedinvalid_destinationrecipient_opted_outtext_too_longtext_emptyinvalid_templatetoo_many_recipientswebhook_url_invalidwebhook_limit_reachedrate_limitedotp_rate_limitedbackpressureinternal_unavailableinternal_error

Error

FieldTypeDescription
error*object