Resala

Resala API

Connect your app to Resala to send SMS and verification codes in Libya. Ready-to-run examples and an instant API key with your phone number.

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/v1

Authentication 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 key

Send a verification code (pins)

Generates a one-time code, texts it, and returns it to you.

POST https://dev.resala.ly/api/v1/pins

Query parameters

service_nameYour app's name, appended to the message.
testSandbox: nothing is sent or charged.
lenDigits in the code: 4, 5 or 6 (default 6).
autofillAndroid App Signature, for auto-fill.
senderYour registered sender name (optional). See "Sender name".

Request body

phone requiredRecipient'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-template

Query parameters

sms_template_id requiredTemplate id, from the Templates page.
testSandbox: priced, but nothing is sent or charged.
senderYour registered sender name (optional). See "Sender name".
scheduled_atScheduled 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 requiredThe rows to send.
phone requiredA 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-view

Query parameters

filterssource:message or source:pin.
qSearch in the text and the recipient's number.
sortsSort column, prefixed with - for descending.
pagePage number, from 1.
paginateRows 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)

acceptedHanded to the mobile network; its report hasn't arrived yet.
sentThe network reports the message as sent.
deliveredThe network reports it as delivered to the handset.
undeliveredThe network reports that it was not delivered.
pendingQueued at the mobile network; its verdict hasn't arrived yet.
failedNo 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"
}
statustypeMeaning
400AccountExpiredYour account's subscription has expired.
400InsufficientCreditNot enough wallet balance to send.
400BadRequestThe request could not be processed.
401UnauthorizedYour token is missing or invalid.
401TokenExpiredYour token has expired, log in again.
403ForbiddenYou don't have permission to do this.
404NotFoundThe requested resource doesn't exist.
422InputValidationOne or more fields failed validation.

API documentation (Postman)

Download this page's endpoints as a ready-to-import Postman collection.

Download Postman collection

In Postman: Import → the downloaded file → Variables → paste your API key into token. The file holds no key, so sharing it is safe.