π± SMS Gateway β API Documentation
Complete REST API reference for the SMS Gateway platform. Send SMS via any Android phone using a SIM card.
Architecture
| Component | Tech | Purpose |
|---|---|---|
| API Backend | Laravel 11 + MySQL | REST API, auth, webhook delivery, job queue |
| Mobile App | Flutter (Android) | Receives jobs via polling, sends SMS via SIM |
| Background Service | Kotlin Foreground Service | Polls API every 5 sec even when app is killed |
| Auth | Laravel Sanctum | Token-based auth for dashboard + API keys |
| Queue | Database driver | Async job processing (optional, device picks up directly) |
Authentication
Two authentication methods are used:
1. Sanctum Token (Dashboard / Mobile App)
Used for user login, device management, message history, and API key management.
# After login, use the token:
Authorization: Bearer YOUR_SANCTUM_TOKEN
2. API Key (External Developers)
Used for sending messages, managing webhooks, and querying message status.
# API Key format: sk_live_xxxxxxxxxxxxxxxxxxxx
X-API-Key: sk_live_abc123def456ghi789
1. Register/Login via
POST /api/v1/auth/login2. Create API key via
POST /api/v1/api-keys3. Use the key in
X-API-Key header for all API calls
Base URL
# Local development http://localhost:8000/api/v1 # Production (replace with your domain) https://your-domain.com/api/v1 # With ngrok (for testing) https://xxxx-xxx-xxx.ngrok-free.dev/api/v1
Rate Limiting
| Endpoint | Limit | Window |
|---|---|---|
POST /messages | 60 requests | Per minute per API key |
| All other endpoints | 120 requests | Per minute |
Rate limit headers returned:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 55
Retry-After: 30 # seconds (only when rate limited)
Error Handling
| HTTP Code | Meaning | Common Cause |
|---|---|---|
| 400 | Bad Request | Invalid JSON or missing fields |
| 401 | Unauthorized | Missing or invalid token/API key |
| 403 | Forbidden | API key disabled / user suspended |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Duplicate message to same number in 30s |
| 422 | Validation Error | Invalid phone format, missing message body |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Server Error | Unexpected server error |
Error response format:
{
"success": false,
"message": "Validation failed",
"errors": {
"to": ["The phone field must be a valid phone number."],
"message": ["The message field is required."]
}
}
π Authentication
Register a new user account.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Full name (2-255 chars) |
email | string | Yes | Valid email address |
password | string | Yes | Min 8 characters |
password_confirmation | string | Yes | Must match password |
Request
curl -X POST https://your-domain.com/api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{ "name": "John Doe", "email": "john@example.com", "password": "secret123", "password_confirmation": "secret123" }'
Response 201
{
"success": true,
"message": "User registered successfully",
"data": {
"user": {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"is_admin": false,
"created_at": "2026-08-27T10:00:00Z"
},
"token": "1|abc123def456..."
}
}
Login and get a Sanctum token.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Registered email |
password | string | Yes | Password |
Request
curl -X POST https://your-domain.com/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "john@example.com", "password": "secret123" }'
Response 200
{
"success": true,
"message": "Login successful",
"data": {
"user": {
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"is_admin": false
},
"token": "2|xyz789abc123..."
}
}
Get current authenticated user profile.
Request
curl -X GET https://your-domain.com/api/v1/auth/me \ -H "Authorization: Bearer YOUR_TOKEN"
Response 200
{
"id": 1,
"name": "John Doe",
"email": "john@example.com",
"is_admin": false,
"created_at": "2026-08-27T10:00:00Z"
}
Revoke the current token and create an audit log.
Request
curl -X POST https://your-domain.com/api/v1/auth/logout \ -H "Authorization: Bearer YOUR_TOKEN"
Response 200
{
"success": true,
"message": "Logged out successfully"
}
π API Keys
List all API keys for the authenticated user.
Request
curl -X GET https://your-domain.com/api/v1/api-keys \ -H "Authorization: Bearer YOUR_TOKEN"
Response 200
{
"success": true,
"data": [
{
"id": 1,
"name": "Production Key",
"key_prefix": "sk_live_abc12...",
"is_active": true,
"last_used_at": "2026-08-27T10:30:00Z",
"created_at": "2026-08-27T10:00:00Z"
}
]
}
Create a new API key. The full key is shown only once.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | A label for this key (e.g., "Production") |
Request
curl -X POST https://your-domain.com/api/v1/api-keys \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Production Key" }'
Response 201
{
"success": true,
"message": "API key created successfully",
"data": {
"id": 1,
"name": "Production Key",
"key": "sk_live_abc123def456ghi789jkl012mno345",
"key_prefix": "sk_live_abc12...",
"created_at": "2026-08-27T10:00:00Z"
}
}
Permanently delete an API key.
Request
curl -X DELETE https://your-domain.com/api/v1/api-keys/1 \ -H "Authorization: Bearer YOUR_TOKEN"
Response 200
{
"success": true,
"message": "API key deleted"
}
π± Devices
List all registered devices for the authenticated user.
Request
curl -X GET https://your-domain.com/api/v1/devices \ -H "Authorization: Bearer YOUR_TOKEN"
Response 200
{
"success": true,
"data": [
{
"id": 1,
"device_id": "android-abc123",
"device_name": "My Phone",
"phone_number": "+919917408207",
"country_code": "IN",
"platform": "android",
"android_version": "14",
"app_version": "1.0.0",
"status": "online",
"last_heartbeat_at": "2026-08-27T10:30:00Z",
"created_at": "2026-08-27T10:00:00Z"
}
]
}
Register a new Android device to send SMS. Called by the Flutter app.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
device_id | string | Yes | Unique device identifier (UUID) |
device_name | string | No | User-friendly name |
phone_number | string | No | SIM phone number |
country_code | string | No | e.g., "IN" |
platform | string | No | "android" |
android_version | string | No | e.g., "14" |
app_version | string | No | e.g., "1.0.0" |
Request
curl -X POST https://your-domain.com/api/v1/devices/register \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "device_id": "android-abc123", "device_name": "My Phone", "phone_number": "+919917408207", "country_code": "IN", "platform": "android", "android_version": "14", "app_version": "1.0.0" }'
Response 201
{
"success": true,
"message": "Device registered successfully",
"data": {
"device_id": 1,
"device_name": "My Phone",
"phone_number": "+919917408207",
"country_code": "IN",
"platform": "android",
"status": "online",
"created_at": "2026-08-27T10:00:00Z"
}
}
Keep the device "online" by sending periodic heartbeats (every 15 sec).
Request
curl -X POST https://your-domain.com/api/v1/devices/1/heartbeat \ -H "Authorization: Bearer YOUR_TOKEN"
Response 200
{
"success": true,
"data": {
"status": "online",
"last_heartbeat_at": "2026-08-27T10:30:00Z",
"device_name": "My Phone",
"phone_number": "+919917408207"
}
}
Unregister a device from the gateway.
Request
curl -X DELETE https://your-domain.com/api/v1/devices/1 \ -H "Authorization: Bearer YOUR_TOKEN"
Response 200
{
"success": true,
"message": "Device deleted"
}
π¬ Messages
queued β Device picks up β processing β SMS sent β sent β deliveredOr on failure:
processing β failed
Send an SMS message. The message is queued and a connected device will pick it up.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
to | string | Yes | Recipient phone with country code (e.g., "+919917408207") |
message | string | Yes | SMS text (max 1600 chars) |
device_id | integer | No | Target a specific device (auto-selected if omitted) |
sim_slot | integer | No | SIM slot (0 or 1, default 0) |
Request
curl -X POST https://your-domain.com/api/v1/messages \ -H "X-API-Key: sk_live_abc123def456" \ -H "Content-Type: application/json" \ -d '{ "to": "+919917408207", "message": "Hello from SMS Gateway!" }'
Response 201
{
"success": true,
"message": "SMS accepted",
"data": {
"id": "msg_5",
"status": "queued"
}
}
List all messages sent via this API key. Paginated.
Query Parameters
| Param | Type | Description |
|---|---|---|
status | string | Filter: queued, processing, sent, delivered, failed, cancelled |
per_page | integer | Results per page (default 15, max 100) |
Request
curl -X GET "https://your-domain.com/api/v1/messages?status=sent&per_page=10" \ -H "X-API-Key: sk_live_abc123def456"
Response 200
{
"data": [
{
"id": "msg_5",
"to": "+919917408207",
"message": "Hello from SMS Gateway!",
"status": "sent",
"sent_at": "2026-08-27T10:30:15Z",
"delivered_at": "2026-08-27T10:30:18Z"
}
],
"links": {
"first": "?page=1",
"last": "?page=3",
"next": "?page=2"
}
}
Get detailed status of a specific message.
Request
curl -X GET https://your-domain.com/api/v1/messages/msg_5 \ -H "X-API-Key: sk_live_abc123def456"
Response 200
{
"id": "msg_5",
"to": "+919917408207",
"message": "Hello from SMS Gateway!",
"status": "sent",
"device_id": 1,
"sent_at": "2026-08-27T10:30:15Z",
"delivered_at": "2026-08-27T10:30:18Z",
"created_at": "2026-08-27T10:30:10Z"
}
Cancel a queued message before it's sent.
Request
curl -X POST https://your-domain.com/api/v1/messages/msg_5/cancel \ -H "X-API-Key: sk_live_abc123def456"
Response 200
{
"success": true,
"message": "Message cancelled"
}
Get message history for the dashboard/mobile app with filtering.
Query Parameters
| Param | Type | Description |
|---|---|---|
status | string | Filter by status |
search | string | Search by phone number |
from | date | Start date (YYYY-MM-DD) |
to | date | End date (YYYY-MM-DD) |
per_page | integer | Results per page (default 20) |
Request
curl -X GET "https://your-domain.com/api/v1/message-history?status=sent&search=9917&from=2026-08-01" \ -H "Authorization: Bearer YOUR_TOKEN"
Response 200
{
"success": true,
"data": {
"data": [...],
"links": {...},
"meta": { "total": 42 }
}
}
Get detailed message info with delivery timeline and attempt history.
Request
curl -X GET https://your-domain.com/api/v1/message-history/msg_5 \ -H "Authorization: Bearer YOUR_TOKEN"
Response 200
{
"id": "msg_5",
"to": "+919917408207",
"message": "Hello from SMS Gateway!",
"status": "delivered",
"device_id": 1,
"device_name": "My Phone",
"sim_slot": 0,
"sent_at": "2026-08-27T10:30:15Z",
"delivered_at": "2026-08-27T10:30:18Z",
"attempts": [
{
"id": 1,
"status": "sent",
"attempted_at": "2026-08-27T10:30:15Z",
"device_id": 1
}
]
}
Re-queue a failed message for retry.
Request
curl -X POST https://your-domain.com/api/v1/message-history/msg_5/retry \ -H "Authorization: Bearer YOUR_TOKEN"
Response 200
{
"success": true,
"message": "Message re-queued for retry"
}
βοΈ Device Internal APIs
These endpoints are called by the Flutter app / Kotlin foreground service to poll for jobs and report results.
X-API-Key and X-Device-ID headers.
Poll for pending SMS jobs. Atomically claims messages (sets status to "processing").
Headers
| Header | Value |
|---|---|
X-API-Key | Your API key |
X-Device-ID | Your device ID |
Request
curl -X GET https://your-domain.com/api/v1/device/jobs \ -H "X-API-Key: sk_live_abc123def456" \ -H "X-Device-ID: 1"
Response 200
{
"success": true,
"jobs": [
{
"id": "msg_5",
"to": "+919917408207",
"message": "Hello from SMS Gateway!",
"sim_slot": 0
}
]
}
Response 200 (no jobs)
{
"success": true,
"jobs": []
}
Report the SMS send result back to the server. Updates message status synchronously.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
status | string | Yes | "sent" or "failed" |
error | string | No | Error message if failed |
error_code | string | No | Telephony error code |
Request (Success)
curl -X POST https://your-domain.com/api/v1/device/messages/5/result \ -H "X-API-Key: sk_live_abc123def456" \ -H "X-Device-ID: 1" \ -H "Content-Type: application/json" \ -d '{ "status": "sent" }'
Request (Failure)
curl -X POST https://your-domain.com/api/v1/device/messages/5/result \ -H "X-API-Key: sk_live_abc123def456" \ -H "X-Device-ID: 1" \ -H "Content-Type: application/json" \ -d '{ "status": "failed", "error": "SMS permission not granted", "error_code": "PERMISSION_DENIED" }'
Response 200
{
"success": true,
"message": "Result recorded",
"data": {
"id": "msg_5",
"status": "sent",
"sent_at": "2026-08-27T10:30:15Z"
}
}
Report SMS delivery confirmation (when carrier confirms delivery).
Request
curl -X POST https://your-domain.com/api/v1/device/messages/5/delivery \ -H "X-API-Key: sk_live_abc123def456" \ -H "X-Device-ID: 1" \ -H "Content-Type: application/json" \ -d '{}'
Response 200
{
"success": true,
"message": "Delivery confirmed",
"data": {
"id": "msg_5",
"status": "delivered",
"delivered_at": "2026-08-27T10:30:18Z"
}
}
Report an incoming SMS received by the device. Triggers webhook for message.received.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
from | string | Yes | Sender phone number |
body | string | Yes | SMS text content |
received_at | datetime | No | ISO 8601 timestamp |
Request
curl -X POST https://your-domain.com/api/v1/device/received-messages \ -H "X-API-Key: sk_live_abc123def456" \ -H "X-Device-ID: 1" \ -H "Content-Type: application/json" \ -d '{ "from": "+919876543210", "body": "Your OTP is 1234", "received_at": "2026-08-27T10:35:00Z" }'
Response 201
{
"success": true,
"message": "Received message saved",
"data": {
"id": 1,
"from": "+919876543210",
"body": "Your OTP is 1234",
"received_at": "2026-08-27T10:35:00Z"
}
}
List all incoming SMS received by your devices.
Request
curl -X GET https://your-domain.com/api/v1/received-messages \ -H "Authorization: Bearer YOUR_TOKEN"
Response 200
{
"success": true,
"data": [
{
"id": 1,
"from": "+919876543210",
"body": "Your OTP is 1234",
"device_id": 1,
"received_at": "2026-08-27T10:35:00Z"
}
]
}
π Webhooks
Webhooks send HTTP POST requests to your URL when message events occur.
List all configured webhooks.
Request
curl -X GET https://your-domain.com/api/v1/webhooks \ -H "X-API-Key: sk_live_abc123def456"
Response 200
{
"success": true,
"data": [
{
"id": 1,
"url": "https://your-app.com/webhook",
"events": ["message.sent", "message.delivered"],
"is_active": true,
"created_at": "2026-08-27T10:00:00Z"
}
]
}
Register a new webhook endpoint.
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
url | string | Yes | HTTPS URL to receive webhooks |
events | array | Yes | Event types to subscribe to |
Request
curl -X POST https://your-domain.com/api/v1/webhooks \ -H "X-API-Key: sk_live_abc123def456" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-app.com/webhook", "events": ["message.sent", "message.delivered", "message.failed"] }'
Response 201
{
"success": true,
"message": "Webhook created",
"data": {
"id": 1,
"url": "https://your-app.com/webhook",
"events": ["message.sent", "message.delivered", "message.failed"],
"is_active": true,
"secret": "whsec_abc123...",
"created_at": "2026-08-27T10:00:00Z"
}
}
Update a webhook URL, events, or active status.
Request
curl -X PUT https://your-domain.com/api/v1/webhooks/1 \ -H "X-API-Key: sk_live_abc123def456" \ -H "Content-Type: application/json" \ -d '{ "url": "https://new-url.com/webhook", "events": ["message.sent", "message.received"], "is_active": true }'
Response 200
{
"success": true,
"message": "Webhook updated"
}
Delete a webhook.
Request
curl -X DELETE https://your-domain.com/api/v1/webhooks/1 \ -H "X-API-Key: sk_live_abc123def456"
Response 200
{
"success": true,
"message": "Webhook deleted"
}
Send a test payload to verify your webhook is working.
Request
curl -X POST https://your-domain.com/api/v1/webhooks/1/test \ -H "X-API-Key: sk_live_abc123def456"
Response 200
{
"success": true,
"message": "Test webhook sent",
"data": {
"status_code": 200,
"response": "OK"
}
}
Built-in test webhook receiver. Configure this as your webhook URL for testing.
curl -X POST https://your-domain.com/api/v1/webhook-receiver \ -H "Content-Type: application/json" \ -d '{ "event": "message.sent", "data": { "id": "msg_5" } }'
π‘οΈ Admin
Admin endpoints require is_admin: true user and Sanctum token.
Get system-wide statistics.
Request
curl -X GET https://your-domain.com/api/v1/admin/dashboard \ -H "Authorization: Bearer ADMIN_TOKEN"
Response 200
{
"users": { "total": 15, "active": 12 },
"devices": { "total": 8, "online": 5 },
"messages": {
"total": 1247,
"sent": 1180,
"delivered": 1150,
"failed": 30,
"queued": 12
}
}
List all users. Also: GET /admin/users/{id},
PATCH /admin/users/{id}/suspend,
PATCH /admin/users/{id}/activate
List all devices across all users. Also: GET /admin/devices/{id},
PATCH /admin/devices/{id}/disable
List all messages across all users. Also: GET /admin/messages/{id}
π Webhook Events & Payloads
| Event | Description |
|---|---|
message.sent | Device reported SMS sent successfully |
message.delivered | Carrier confirmed SMS delivery |
message.failed | SMS send failed |
message.received | Device received an incoming SMS |
Webhook Payload
{
"event": "message.sent",
"timestamp": "2026-08-27T10:30:15Z",
"data": {
"id": "msg_5",
"to": "+919917408207",
"message": "Hello from SMS Gateway!",
"status": "sent",
"device_id": 1,
"sent_at": "2026-08-27T10:30:15Z"
}
}
Webhook Headers
| Header | Value |
|---|---|
X-SMS-Event | Event type (e.g., "message.sent") |
X-SMS-Signature | HMAC-SHA256 signature (verify with webhook secret) |
Content-Type | application/json |
π¦ Data Models
Message
| Field | Type | Description |
|---|---|---|
id | string | msg_1, msg_2, ... |
to | string | Recipient phone number |
message | string | SMS text content |
status | enum | queued / processing / sent / delivered / failed / cancelled |
device_id | integer | ID of the device that sent it |
sim_slot | integer | SIM slot used (0 or 1) |
failure_reason | string | Error message if failed |
failure_code | string | Telephony error code |
sent_at | datetime | When SMS was sent |
delivered_at | datetime | When delivery was confirmed |
created_at | datetime | When message was queued |
Device
| Field | Type | Description |
|---|---|---|
id | integer | Auto-increment ID |
device_id | string | Unique device UUID |
device_name | string | User-friendly name |
phone_number | string | SIM phone number |
status | enum | online / offline / disabled |
last_heartbeat_at | datetime | Last heartbeat timestamp |
API Key
| Field | Type | Description |
|---|---|---|
id | integer | Auto-increment ID |
name | string | Label |
key_prefix | string | First 16 chars of key |
key_hash | string | SHA-256 hash (key never stored in plain) |
is_active | boolean | Enabled/disabled |
last_used_at | datetime | Last API call timestamp |
π Quick Start Examples
Step 1: Register & Login
# Register curl -X POST http://localhost:8000/api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{"name":"John","email":"john@test.com","password":"secret123","password_confirmation":"secret123"}' # Login curl -X POST http://localhost:8000/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"john@test.com","password":"secret123"}'
Step 2: Create API Key
curl -X POST http://localhost:8000/api/v1/api-keys \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{"name":"My App"}'
Step 3: Send SMS
curl -X POST http://localhost:8000/api/v1/messages \ -H "X-API-Key: sk_live_..." \ -H "Content-Type: application/json" \ -d '{"to":"+919917408207","message":"Hello!"}'
Step 4: Check Status
curl -X GET http://localhost:8000/api/v1/messages/msg_1 \
-H "X-API-Key: sk_live_..."
Python Example
import requests BASE = "http://localhost:8000/api/v1" # Login r = requests.post(f"{BASE}/auth/login", json={ "email": "john@test.com", "password": "secret123" }) token = r.json()["data"]["token"] # Send SMS r = requests.post(f"{BASE}/messages", headers={"X-API-Key": "sk_live_..."}, json={"to": "+919917408207", "message": "Hello!"} ) print(r.json()) # {success: true, data: {id: "msg_1", status: "queued"}}
JavaScript / Node.js Example
const response = await fetch("http://localhost:8000/api/v1/messages", { method: "POST", headers: { "Content-Type": "application/json", "X-API-Key": "sk_live_..." }, body: JSON.stringify({ to: "+919917408207", message: "Hello from Node.js!" }) }); const data = await response.json(); console.log(data); // {success: true, data: {id: "msg_1", status: "queued"}}
PHP Example
<?php $ch = curl_init("http://localhost:8000/api/v1/messages"); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_HTTPHEADER => [ "Content-Type: application/json", "X-API-Key: sk_live_...", ], CURLOPT_POSTFIELDS => json_encode([ "to" => "+919917408207", "message" => "Hello from PHP!", ]), CURLOPT_RETURNTRANSFER => true, ]); $result = json_decode(curl_exec($ch), true); print_r($result);