SMS API
The SMS API sends messages from your sender lines, reads what was sent and received, and manages templates, blocked numbers and the webhook. The arianet sms CLI commands are built on it. The wallet, top-ups, tickets and team members belong to the Arianet account and are not part of this API.
- Base URL:
https://api.ariaservice.net/sms/v1 - Format: JSON over HTTPS
- Authentication: a personal API token sent as a Bearer token (Authentication)
- OpenAPI 3.0:
https://api.ariaservice.net/sms/v1/openapi.json, browsable in the interactive reference
The previous sms-api.ariaservice.net/api/v1 addresses and their keys keep working during the migration window; their answers carry Deprecation: true and a link to this page. Use this API and your Arianet account token for new work.
Quick start
- In the panel, at cloud.ariaservice.net/users/api-tokens, create a token with the scopes you need (table below).
- List your sender lines and send a message from one of them (
fromis the line's number or itsid):
export ARIANET_TOKEN="paste-your-token-here"
BASE=https://api.ariaservice.net/sms/v1
AUTH="Authorization: Bearer $ARIANET_TOKEN"
curl -s $BASE/lines -H "$AUTH"
curl -s -X POST $BASE/messages -H "$AUTH" -H "Content-Type: application/json" \
-d '{"from":"300074360","recipients":["09121234567","09351234567"],"body":"Your code is 1234","client_reference":"order-981"}'
The answer is 202 Accepted:
{"batch_id":"01JC8Z...","status":"processing","total_recipients":2,"total_cost_rial":3000,"filtered_blocked_count":0}
A send creates a batch and charges the account's wallet; follow it with GET /batches/{id} and GET /messages?batch_id=.... Numbers on your blocked list are skipped (filtered_blocked_count). The price per message, and whether it is charged per message or per SMS part, is in GET /prices.
Sending
POST /messages takes:
| Field | Type | |
|---|---|---|
recipients | array of strings, 1-100 | mobile numbers (required) |
from | digit string | the sender line's number or id from GET /lines (required) |
body | string, at most 2000 characters | the text; required unless template_id is given |
template_id | integer | send one of your templates instead of body |
scheduled_at | ISO 8601 time, in the future | send later instead of now |
client_reference | string | your reference for this send; when absent the Idempotency-Key header is used. A send repeated with the same reference is not sent twice |
With scheduled_at nothing is sent or charged yet: the answer is {"scheduled_id":12,"status":"pending","scheduled_at":"..."}, the send is listed in GET /scheduled and can be cancelled with DELETE /scheduled/{id} while it is pending.
Lists and pages
Every list takes page and per_page (at most 200) and answers:
{"data":[...],"meta":{"total":134,"current_page":1,"per_page":50}}
GET /messages also filters by batch_id, status and to; GET /blocked-recipients by phone.
Authentication and scopes
| Scope | Allows |
|---|---|
sms:messages:read | sent and received messages, batches, scheduled sends, prices and the report |
sms:messages:write | sending and cancelling scheduled sends, blocked numbers and the webhook |
sms:lines:read | sender lines |
sms:templates:read | templates |
sms:templates:write | creating, changing and deleting templates |
A token without any scope is unrestricted; asking for a dedicated line (POST /lines) needs such a token. SMS runs in region iran-1 only and needs no Arianet-Region header.
Resources
| Method and path | Purpose |
|---|---|
POST /messages | send now or at scheduled_at (see Sending) |
GET /messages, GET /messages/{id} | sent messages and their delivery |
GET /batches, GET /batches/{id} | send batches |
GET /scheduled, DELETE /scheduled/{id} | scheduled sends; cancel a pending one |
GET /inbound | messages received on your lines |
GET /lines, POST /lines | sender lines; ask for a dedicated line (number, note) |
GET /templates, POST /templates, GET/PUT/DELETE /templates/{id} | templates (name, body) |
GET /blocked-recipients, POST /blocked-recipients, DELETE /blocked-recipients/{id} | blocked numbers; input phone and note, output mobile and reason |
GET /webhook, PUT /webhook, POST /webhook/secret | the webhook (url, enabled, events) and a new signing secret; see Webhooks |
GET /reports/summary | sent, delivered, failed and spend, with a daily breakdown (from, to) |
GET /prices | price per message and its volume tiers |
The request and answer of every endpoint are in the interactive reference.
Statuses
| Object | status values |
|---|---|
Message (GET /messages) | queued, sent, delivered, failed, undelivered, rejected, expired |
Batch (GET /batches) | pending, processing, partial (some failed), completed, failed |
Scheduled (GET /scheduled) | scheduled, processing, sent, failed, cancelled |
The status filter on GET /messages and GET /batches takes these values.
Message detail and timeline
Every message in GET /messages also has:
| Field | Meaning |
|---|---|
operator | Recipient operator from the number prefix: mci, mtn, rightel, other (best effort for ported numbers) |
encoding | unicode or gsm |
queued_at | When the message was queued |
provider.name | Sending gateway, e.g. magfa |
provider.message_id | The operator's (Magfa) tracking id |
provider.status_code | Last operator status code: 0 no status yet, 8 reached the operator, 1 delivered to the handset, 2 not delivered to the handset, 16 never reached the operator, -1 expired |
provider.error_code | Magfa's error code when it refused the message |
delivery_checks | Number of delivery-status checks |
last_checked_at | Time of the last check |
GET /messages/{id} also returns timeline: the steps in time order, each with type, at, status_code and error_code.
type | Meaning |
|---|---|
queued | Queued |
sent_to_provider | Accepted by Magfa, tracking id assigned |
provider_refused | Refused by Magfa (refunded) |
status_checked | Status check with no final result yet |
delivered_to_operator | Reached the operator's network, waiting for the handset |
delivered | Delivered to the handset |
not_delivered | Not delivered to the handset |
not_sent_to_operator | Never reached the operator's network |
expired | No delivery report within 24 hours |
rejected | Rejected |
Every batch in GET /batches also has channel (panel, api or system), sender_ip (masked, like 85.133.133.x) and completed_at.
Webhooks
Set the URL, whether it is on, and the events you want with PUT /webhook:
curl -s -X PUT $BASE/webhook -H "$AUTH" -H "Content-Type: application/json" \
-d '{"url":"https://example.com/sms-hook","enabled":true,"events":["message.delivered","message.failed","inbound.received"]}'
| Event | When |
|---|---|
batch.completed | a send finished; always sent while the webhook is on |
message.delivered | a message reached the recipient |
message.failed | a message will never be delivered (failed, undelivered, rejected, expired) |
inbound.received | an SMS arrived on one of your dedicated lines |
The last three are sent only when listed in events; they are off by default.
Each event is a POST of {"event","occurred_at","data"}, where data has the same shape the API returns for that object:
{"event":"message.delivered","occurred_at":"2026-11-12T17:20:00+00:00","data":{"id":"01m78c...","batch_id":"01m78c...","to":"09121234567","from":"300074360","body":"Your code is 1234","parts":1,"status":"delivered","cost_rial":1750,"error":null,"sent_at":"...","delivered_at":"...","created_at":"..."}}
The X-Signature header is the HMAC-SHA256 of the raw body keyed with your webhook signing secret; verify it. A non-2xx answer is retried up to 5 times with growing delays.
Errors
Errors are JSON: {"code": "...", "message": "..."}.
| HTTP | Meaning |
|---|---|
| 401 | no token, or an invalid one |
| 402 | insufficient_balance |
| 403 | insufficient_scope, forbidden, identity_needed, account_suspended, or sender_line_not_usable (the line is not yours to send from) |
| 404 | not_found (for example a template of another account) |
| 422 | validation_failed, all_recipients_blocked, already_blocked, not_cancellable |
| 429 | rate_limit_exceeded / rate_limited |
| 503 | sms_unavailable, region_unavailable |
| 504 | gateway_timeout: no answer in time; the send may still have happened, check GET /messages before retrying (that is what client_reference is for) |