Send from your own application — OTP, order updates, reminders — over a REST API built so that a network timeout on your side cannot charge you twice.
Your HTTP client has a timeout. Ours has a network. Sooner or later your request will time out after we created the message but before you got the response — and your retry logic will send it again. Without protection that is a double charge and two SMS to every recipient, and neither is refundable because the operator already billed for both.
Every send accepts an Idempotency-Key header. Retry the same request as many times
as you like: the first call does the work, every later call returns the identical response
including the same message ID, and nothing is charged twice.
curl https://hantarsms.com/api/v1/messages \
-H "X-API-Key: $HANTARSMS_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-48213-reminder" \
-d '{"to":["60123456789"],"text":"Your order #48213 is ready for pickup."}'
Totals are not much use when a campaign underperforms. The API returns the outcome for each individual number, and it distinguishes two states that many providers collapse into one:
| Status | Meaning | Charged |
|---|---|---|
delivered | Operator confirmed handset receipt | Yes |
undelivered | Accepted by operator, never reached the handset | Yes |
rejected | Never submitted — invalid number or blocked content | No, refunded |
sent | Submitted, final receipt not in yet | Yes |
Merging rejected into undelivered makes your invoice impossible to
reconcile, because one is billable and the other is not.
insufficient_balance and content_blocked, not prose you have to
string-match.Endpoints, parameters, every error code and working examples in PHP, Python, Node.js and Java are in the API documentation. It is readable without an account — worth a look before you register, to confirm it fits how your system is built.
No. The API costs the same per segment as sending from the web console, with no platform fee, no minimum volume and no separate API plan. You pay for messages.
If you sent an Idempotency-Key header, nothing — the retry returns the original response and the same message ID, and you are charged once. Without that header a retry is treated as a new message, so send the header on every request.
Yes, and that is one of the main uses. Messages go out from a shared 5-digit short code, which is the sender format users expect for one-time passwords. See short code SMS.
Poll the message endpoint for a summary, or the recipients endpoint for the per-number breakdown. Status is refreshed from the operator when you read it. Webhook push is on the roadmap; polling is the supported method today.
The documentation has complete working examples in curl, PHP, Python, Node.js and Java. It is a plain REST API with JSON, so any HTTP client works — there is no SDK to install.
Register, generate a key from the console, send a test to your own phone on trial credit.