SMSSMS Gateway API v1

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:

AbilityAllows
sms.sendSend single SMS messages (sync and async)
sms.bulkSend bulk SMS to multiple recipients
messages.readRead the status of its own messages
stats.readRead its own sending statistics
Keep the token on a server. A token placed in a mobile app or in browser JavaScript can be extracted by anyone and used to send SMS at your expense. Mobile and web front ends should call their own backend, which then calls the gateway.

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

POST/sms/send

Sends now and returns the final result in the same request. Use it for OTP and verification codes. Requires sms.send.

FieldTypeRules
tostringRequired. A valid phone number.
messagestringRequired. 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)

POST/sms/send-async

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

POST/sms/send-bulk

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

GET/sms/{id}

Returns one of your messages. Another project's message returns 404. Requires messages.read.

GET/sms?status=failed&batch_id=…&per_page=25&page=1

Lists your messages, newest first, with pagination details in meta.

GET/sms/batches/{batch_id}

Progress of a bulk batch: total and a count per status.

Statistics & token check

GET/stats

Your own totals: total, sent, delivered, failed, pending, today, this_month, segments. Requires stats.read.

GET/me

Confirms the token works and returns the client's name, abilities, limits and messages sent today. Useful in a deployment check.

Health

GET/health

No authentication. 200 with {"success": true, "status": "ok"} when the gateway and its database are up, 503 otherwise.

GET/provider-health

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:

SettingValue
API URLhttps://sms.befoundonline.ps/api/compat/supercode/SendSMS
API keyThe project's gateway token (sgw_…), not a SuperCode key
Sender nameUnchanged (a sender set on the client in the gateway takes precedence)
GET/compat/supercode/SendSMS?id={token}&sender={sender}&to=970599123456&msg={text}

Same parameters (id, sender, to, msg; also accepted as a POST form) and the same plain-text answers as SuperCode:

HTTPBodyWhen
200The provider's answerSent (while SuperCode is the provider, its own answer is returned unchanged)
400Invalid ID Parameter, Invalid To Parameter, Invalid Msg Parameter, Invalid Sender ParameterMissing or invalid parameter
401Authentication FailedUnknown, revoked or expired token, or disabled client
403ForbiddenClient not allowed to send, or IP not allowed
429Too Many Requests / Daily Quota ExceededRate limit or daily quota
5xxUnable 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

StatusMeaning
pendingBeing sent synchronously right now.
queuedWaiting in the queue, or waiting to be retried after a temporary failure.
sentAccepted by the SMS provider.
deliveredThe provider confirmed delivery to the handset (only when the provider sends delivery reports).
failedNot sent. See error.code.

Errors

CodeHTTPMeaning
UNAUTHENTICATED401The API token is missing, invalid, expired or revoked.
FORBIDDEN403This API client is not allowed to perform this action.
HTTPS_REQUIRED403Requests must be made over HTTPS.
NOT_FOUND404The requested resource was not found.
METHOD_NOT_ALLOWED405The HTTP method is not supported for this endpoint.
VALIDATION_ERROR422The request payload is invalid.
HTTP_ERROR500The request could not be processed.
SMS_RATE_LIMITED429Too many requests. Slow down and retry after the indicated delay.
SMS_QUOTA_EXCEEDED429The daily SMS quota for this API client has been reached.
SMS_INVALID_NUMBER422The SMS provider rejected the destination number.
SMS_PROVIDER_ERROR502The SMS provider rejected the message.
SMS_AUTHENTICATION_ERROR502The gateway could not authenticate with the SMS provider.
SMS_PROVIDER_UNAVAILABLE503The SMS provider is temporarily unavailable.
SMS_TIMEOUT504The SMS provider did not respond in time.
SMS_UNKNOWN_ERROR500An 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

The token must not be compiled into a mobile app: it can be extracted from the binary. The Flutter app should call your own backend, which calls the gateway with the token. The code below is for Dart running on a server, or for an internal tool.
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