About this API
Send verification codes and messages from your app, and read back what happened to each one.
Base URL
https://dev.resala.ly/api/v1Authentication header
Authorization: Bearer <API_KEY>You need an API key to run these examples. Press "Get an API key" and we'll text a code to your phone.
Get an API keySend a verification code (pins)
Generates a one-time code, texts it, and returns it to you.
POST https://dev.resala.ly/api/v1/pinsQuery parameters
service_name | Your app's name, appended to the message. |
test | Sandbox: nothing is sent or charged. |
len | Digits in the code: 4, 5 or 6 (default 6). |
autofill | Android App Signature, for auto-fill. |
sender | Your registered sender name (optional). See "Sender name". |
Request body
phone required | Recipient's number in international form. Libyan: 091, 092, 093 or 094 + 7 digits. Non-Libyan numbers are accepted here only. |
{
"phone": "218910001234"
}Example request
curl -X POST "https://dev.resala.ly/api/v1/pins?service_name=MyApp" \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"phone":"218910001234"}'Successful response (201)
{
"id": "9b1f5b7e-3f1a-4c2e-8a5e-6f3d2c1b0a9e",
"pin": "482913",
"code": "218",
"region": "LY",
"number": "910001234",
"content": "رمز التحقق: 482913 - MyApp",
"hit_count": 0,
"created_at": "2026-09-16T12:00:00Z",
"updated_at": "2026-09-16T12:00:00Z",
"sender_name": "MyBrand"
}- Verifying is your job: store pin on your server and compare it with what the user types.
Send a templated message (send-template)
Sends an approved template to one or more recipients, filling its variables.
POST https://dev.resala.ly/api/v1/messages/send-templateQuery parameters
sms_template_id required | Template id, from the Templates page. |
test | Sandbox: priced, but nothing is sent or charged. |
sender | Your registered sender name (optional). See "Sender name". |
scheduled_at | Scheduled send time (optional): YYYY-MM-DD HH:MM in Libya time or RFC 3339, in the future and within 30 days. Ignored with test. |
Request body
records required | The rows to send. |
phone required | A Libyan number: 091, 092, 093 or 094 + 7 digits. Any other is not sent or charged, and comes back in invalid_numbers. |
variables | $1, $2 … named exactly as in the template. |
{
"records": [
{ "phone": "218910001234", "$1": "رسالة", "$2": "أحمد علي" },
{ "phone": "218910001234", "$1": "رسالة", "$2": "سارة", "scheduled_at": "2026-10-20 09:00" }
]
}Example request
curl -X POST "https://dev.resala.ly/api/v1/messages/send-template?sms_template_id=TEMPLATE_ID" \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"records":[{"phone":"218910001234","$1":"رسالة","$2":"أحمد علي"}]}'Successful response (201)
{
"is_prod": true,
"total_messages": 1,
"total_free_messages": 0,
"total_sent_free_messages": 0,
"total_cost": 0.05,
"succeeded": 1,
"failed": 0,
"failed_numbers": [],
"invalid_numbers": [],
"sender_names": { "libyana": "MyBrand", "almadar": "MyBrand" },
"sms_template_version": {
"id": "6d2f9c41-7b8e-4a10-9c33-1e5a7d0b4f22",
"version_number": 3,
"body": "أهلاً بك في $1، نحن سعداء بانضمامك يا $2",
"status": "APPROVED"
}
}- Scheduling: pass scheduled_at as YYYY-MM-DD HH:MM in Libya time (or RFC 3339). In the query string, encode the value: a space as %20 and + as %2B (or use the Z form, e.g. 2026-10-20T07:00:00Z). You can also put scheduled_at inside any record in records to give it its own time (or a column in a CSV file); a record's own time wins over the parameter, and records without a time are sent at once when the parameter is not passed. The time must be in the future and within 30 days. A scheduled send is accepted in the background whatever the number of recipients: the response carries batch_id, queued: true and scheduled_at, the cost is charged at scheduling, and it cannot be cancelled. Follow it with GET /message-batches/{id} (status scheduled, running, completed).
Message history (sent-view)
Everything you sent, codes and messages, with the state each reached.
GET https://dev.resala.ly/api/v1/sent-viewQuery parameters
filters | source:message or source:pin. |
q | Search in the text and the recipient's number. |
sorts | Sort column, prefixed with - for descending. |
page | Page number, from 1. |
paginate | Rows per page. |
Example request
curl "https://dev.resala.ly/api/v1/sent-view?filters=source:message|pin&paginate=20" \
-H "Authorization: Bearer TOKEN"Successful response (200)
{
"data": [
{
"id": "1f0c8a54-2b77-4d6e-9f31-0a8b7c6d5e4f",
"code": "218",
"region": "LY",
"number": "910001234",
"content": "نص الرسالة",
"source": "message",
"status": "delivered",
"pages": 1,
"env": "production",
"created_at": "2026-09-16T12:00:00Z",
"updated_at": "2026-09-16T12:05:00Z"
}
],
"meta": {
"total": 1,
"per_page": 20,
"current_page": 1,
"last_page": 1
}
}- status updates after the send, so poll here instead of expecting it in the send response.
- meta's introspection fields (columns and others) are left out of the example.
Sender name (sender)
Send under your brand instead of the default name by adding the sender parameter to the send request URL.
- Accepted by every send request: OTPs (/pins), single messages, template sends and bulk sends, as a URL parameter: ?sender=MyBrand.
- Write the name exactly as registered; upper and lower case differ: MyBrand is not mybrand.
- The name must be active and unexpired for your account on the recipient's carrier (Libyana or Almadar); a name active on one carrier is not used for the other carrier's numbers.
- Without sender, or when the above does not hold, the message goes out under your default name on the recipient's carrier, or as Resala if you have none active there. The request does not return an error in that case.
- The name the message went out under comes back in the response: sender_name for an OTP or a single message, and sender_names (one per network: libyana or almadar) for template and bulk sends. It is also in the message log (/sent-view).
Example: a template send as MyBrand
curl -X POST "https://dev.resala.ly/api/v1/messages/send-template?sms_template_id=TEMPLATE_ID&sender=MyBrand" \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{"records":[{"phone":"218910001234","$1":"رسالة"}]}'Message states (status)
accepted | Handed to the mobile network; its report hasn't arrived yet. |
sent | The network reports the message as sent. |
delivered | The network reports it as delivered to the handset. |
undelivered | The network reports that it was not delivered. |
pending | Queued at the mobile network; its verdict hasn't arrived yet. |
failed | No network accepted it; it will not be resent. |
Error reference
Branch on type, not on message: the latter is translated and may change. Quote request_id when contacting support.
{
"status": 400,
"type": "AccountExpired",
"message": "لقد انتهت صلاحية حسابك في رسالة، رجاء قم بتجديد الصلاحية لاستخدام الخدمة",
"request_id": "01JQ8Z4K7M"
}| status | type | Meaning |
|---|---|---|
| 400 | AccountExpired | Your account's subscription has expired. |
| 400 | InsufficientCredit | Not enough wallet balance to send. |
| 400 | BadRequest | The request could not be processed. |
| 401 | Unauthorized | Your token is missing or invalid. |
| 401 | TokenExpired | Your token has expired, log in again. |
| 403 | Forbidden | You don't have permission to do this. |
| 404 | NotFound | The requested resource doesn't exist. |
| 422 | InputValidation | One or more fields failed validation. |
API documentation (Postman)
Download this page's endpoints as a ready-to-import Postman collection.
Download Postman collectionIn Postman: Import → the downloaded file → Variables → paste your API key into token. The file holds no key, so sharing it is safe.