SMS Gateway API
One SMS API for every project. The SMS provider, its credentials, sender ID and request format live only inside the gateway, so the provider can change without any change to your project.
Base URL
https://sms.befoundonline.ps/api/v1
All requests and responses use JSON. Send Accept: application/json and, for POST requests, Content-Type: application/json. HTTPS is required in production.
Add two values to your project's .env:
SMS_GATEWAY_URL=https://sms.befoundonline.ps SMS_GATEWAY_TOKEN=your-project-token
Authentication
Every request except /health needs your project's API token in the Authorization header:
Authorization: Bearer YOUR_PROJECT_TOKEN
Tokens are issued by the gateway administrator, one client per project. A token is shown once when it is created and is stored hashed, so a lost token cannot be recovered: ask for a new one.
Each client is granted abilities that decide which endpoints it may call:
| Ability | Allows |
|---|---|
sms.send | Send single SMS messages (sync and async) |
sms.bulk | Send bulk SMS to multiple recipients |
messages.read | Read the status of its own messages |
stats.read | Read its own sending statistics |
Response format
Every response uses the same envelope, whichever provider is used behind the scenes.
Success
{
"success": true,
"message": "SMS sent successfully",
"data": {
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"message_id": "provider-message-id",
"status": "sent",
"to": "+970591234567",
"segments": 1,
"encoding": "gsm7",
"batch_id": null,
"error": null,
"created_at": "2026-01-01T10:00:00+00:00",
"sent_at": "2026-01-01T10:00:01+00:00",
"delivered_at": null,
"failed_at": null
}
}
id is the gateway's identifier for the message: use it to check the status later. message_id is the identifier assigned by the SMS provider.
Failure
{
"success": false,
"message": "Unable to send SMS",
"error": {
"code": "SMS_PROVIDER_ERROR",
"description": "The SMS provider rejected the message.",
"reference": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b"
}
}
Always branch on error.code, never on the message text. Validation errors add an error.errors object with the messages for each field. Every response carries an X-Request-Id header: quote it when reporting a problem.
Rate limits & quotas
Each client has its own limit (default 60 requests per minute); one client never affects another. Every response includes:
X-RateLimit-Limit: 60 X-RateLimit-Remaining: 59
When the limit is exceeded the gateway answers 429 with a Retry-After header:
{
"success": false,
"message": "Rate limit exceeded. Retry after 42 seconds.",
"error": { "code": "SMS_RATE_LIMITED", "retry_after": 42 }
}
A client may also have a daily SMS quota. Once it is reached, sends return 429 with SMS_QUOTA_EXCEEDED until midnight (gateway time). A bulk request counts each recipient against the quota.
Phone numbers
All of these formats are accepted and normalized to international format (E.164):
0591234567 local, the gateway's default country code is added +970591234567 international 00970591234567 international with 00 970591234567 international without +
Spaces, dashes and parentheses are ignored. The gateway may restrict destination countries; a number outside them fails validation.
Send SMS
Sends now and returns the final result in the same request. Use it for OTP and verification codes. Requires sms.send.
| Field | Type | Rules |
|---|---|---|
to | string | Required. A valid phone number. |
message | string | Required. At most 1000 characters. Arabic text uses UCS-2: 70 characters per SMS, 67 per part when split. |
{
"to": "0591234567",
"message": "Your verification code is 123456"
}
Responses: 200 sent · 422 invalid input · 429 rate limit or quota · 502/503/504 the provider failed (see errors). The message is recorded even when it fails: error.reference is its id.
Send SMS (queued)
Same body as /sms/send. Returns 202 immediately with "status": "queued"; the gateway sends from its queue and retries temporary provider failures automatically. Poll GET /sms/{id} for the result. Requires sms.send.
Send bulk SMS
Sends the same message to up to 1,000 recipients. Always queued, so a large list never times out. Numbers that normalize to the same destination are sent once. Requires sms.bulk.
{
"recipients": ["0591111111", "0592222222", "+970593333333"],
"message": "Hello"
}
Response 202:
{
"success": true,
"message": "Bulk SMS queued for delivery",
"data": {
"batch_id": "0199a1b2-0000-7000-8000-000000000000",
"status": "queued",
"total": 3,
"duplicates_removed": 0,
"messages": [ { "id": "…", "to": "+970591111111", "status": "queued", "…": "…" } ]
}
}
Track progress with GET /sms/batches/{batch_id}, which returns the count of messages in each status.
Message status
Returns one of your messages. Another project's message returns 404. Requires messages.read.
Lists your messages, newest first, with pagination details in meta.
Progress of a bulk batch: total and a count per status.
Statistics & token check
Your own totals: total, sent, delivered, failed, pending, today, this_month, segments. Requires stats.read.
Confirms the token works and returns the client's name, abilities, limits and messages sent today. Useful in a deployment check.
Health
No authentication. 200 with {"success": true, "status": "ok"} when the gateway and its database are up, 503 otherwise.
Authenticated. Whether the SMS provider is reachable ("status": "ok" or "unavailable"), cached for about a minute.
Projects written for SuperCode
A project that already calls SuperCode directly can use the gateway without any code change. In its SMS settings, change only:
| Setting | Value |
|---|---|
| API URL | https://sms.befoundonline.ps/api/compat/supercode/SendSMS |
| API key | The project's gateway token (sgw_…), not a SuperCode key |
| Sender name | Unchanged (a sender set on the client in the gateway takes precedence) |
Same parameters (id, sender, to, msg; also accepted as a POST form) and the same plain-text answers as SuperCode:
| HTTP | Body | When |
|---|---|---|
| 200 | The provider's answer | Sent (while SuperCode is the provider, its own answer is returned unchanged) |
| 400 | Invalid ID Parameter, Invalid To Parameter, Invalid Msg Parameter, Invalid Sender Parameter | Missing or invalid parameter |
| 401 | Authentication Failed | Unknown, revoked or expired token, or disabled client |
| 403 | Forbidden | Client not allowed to send, or IP not allowed |
| 429 | Too Many Requests / Daily Quota Exceeded | Rate limit or daily quota |
| 5xx | Unable To Send SMS (SMS_…) | The provider failed; the code is one of the error codes |
The token travels in the URL, as SuperCode's key did, so it can appear in web server logs. New code should use POST /sms/send with the Authorization header instead.
Message statuses
| Status | Meaning |
|---|---|
pending | Being sent synchronously right now. |
queued | Waiting in the queue, or waiting to be retried after a temporary failure. |
sent | Accepted by the SMS provider. |
delivered | The provider confirmed delivery to the handset (only when the provider sends delivery reports). |
failed | Not sent. See error.code. |
Errors
| Code | HTTP | Meaning |
|---|---|---|
UNAUTHENTICATED | 401 | The API token is missing, invalid, expired or revoked. |
FORBIDDEN | 403 | This API client is not allowed to perform this action. |
HTTPS_REQUIRED | 403 | Requests must be made over HTTPS. |
NOT_FOUND | 404 | The requested resource was not found. |
METHOD_NOT_ALLOWED | 405 | The HTTP method is not supported for this endpoint. |
VALIDATION_ERROR | 422 | The request payload is invalid. |
HTTP_ERROR | 500 | The request could not be processed. |
SMS_RATE_LIMITED | 429 | Too many requests. Slow down and retry after the indicated delay. |
SMS_QUOTA_EXCEEDED | 429 | The daily SMS quota for this API client has been reached. |
SMS_INVALID_NUMBER | 422 | The SMS provider rejected the destination number. |
SMS_PROVIDER_ERROR | 502 | The SMS provider rejected the message. |
SMS_AUTHENTICATION_ERROR | 502 | The gateway could not authenticate with the SMS provider. |
SMS_PROVIDER_UNAVAILABLE | 503 | The SMS provider is temporarily unavailable. |
SMS_TIMEOUT | 504 | The SMS provider did not respond in time. |
SMS_UNKNOWN_ERROR | 500 | An unexpected error occurred. |
SMS_RATE_LIMITED, SMS_PROVIDER_UNAVAILABLE and SMS_TIMEOUT are temporary. Never retry /sms/send blindly after SMS_TIMEOUT: the provider may have sent the message. Prefer /sms/send-async when a duplicate SMS would be a problem.
cURL
curl -X POST https://sms.befoundonline.ps/api/v1/sms/send \
-H "Authorization: Bearer YOUR_PROJECT_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"to": "0591234567", "message": "Your verification code is 123456"}'
Laravel / PHP
config/services.php
'sms_gateway' => [
'url' => env('SMS_GATEWAY_URL'),
'token' => env('SMS_GATEWAY_TOKEN'),
],
app/Services/SmsGateway.php
<?php
namespace App\Services;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Facades\Http;
class SmsGateway
{
/**
* @return array The "data" object of the response.
*
* @throws \Illuminate\Http\Client\RequestException When the gateway returns an error.
*/
public function send(string $to, string $message): array
{
return $this->client()
->post('/sms/send', ['to' => $to, 'message' => $message])
->throw()
->json('data');
}
public function sendLater(string $to, string $message): array
{
return $this->client()
->post('/sms/send-async', ['to' => $to, 'message' => $message])
->throw()
->json('data');
}
private function client(): PendingRequest
{
return Http::baseUrl(rtrim(config('services.sms_gateway.url'), '/').'/api/v1')
->withToken(config('services.sms_gateway.token'))
->acceptJson()
->connectTimeout(5)
->timeout(30);
}
}
Usage, with error handling:
use Illuminate\Http\Client\RequestException;
try {
$sms = app(\App\Services\SmsGateway::class)->send('0591234567', 'Your code is 123456');
} catch (RequestException $exception) {
$code = $exception->response->json('error.code'); // e.g. SMS_PROVIDER_ERROR
report($exception);
}
Plain PHP without Laravel:
<?php
$curl = curl_init(getenv('SMS_GATEWAY_URL').'/api/v1/sms/send');
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer '.getenv('SMS_GATEWAY_TOKEN'),
'Content-Type: application/json',
'Accept: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['to' => '0591234567', 'message' => 'Hello']),
]);
$result = json_decode(curl_exec($curl), true);
curl_close($curl);
if (! ($result['success'] ?? false)) {
error_log('SMS failed: '.($result['error']['code'] ?? 'unknown'));
}
JavaScript (Node.js 18+)
async function sendSms(to, message) {
const response = await fetch(`${process.env.SMS_GATEWAY_URL}/api/v1/sms/send`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.SMS_GATEWAY_TOKEN}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({ to, message }),
signal: AbortSignal.timeout(30_000),
});
const result = await response.json();
if (!result.success) {
throw new Error(`${result.error.code}: ${result.message}`);
}
return result.data; // { id, message_id, status, ... }
}
await sendSms('0591234567', 'Your verification code is 123456');
Run this on a server. Never ship the token in browser code.
Flutter / Dart
import 'dart:convert';
import 'package:http/http.dart' as http;
class SmsGateway {
SmsGateway({required this.baseUrl, required this.token});
final String baseUrl;
final String token;
Future<Map<String, dynamic>> send(String to, String message) async {
final response = await http
.post(
Uri.parse('$baseUrl/api/v1/sms/send'),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: jsonEncode({'to': to, 'message': message}),
)
.timeout(const Duration(seconds: 30));
final body = jsonDecode(response.body) as Map<String, dynamic>;
if (body['success'] != true) {
throw Exception('${body['error']['code']}: ${body['message']}');
}
return body['data'] as Map<String, dynamic>;
}
}
Machine-readable specification: openapi.json (import into Postman or Insomnia) · Swagger UI