Reference
API Documentation
Every endpoint, parameter, and response for the MySMSAPio SMS Gateway. All routes are prefixed with /api/v1.
Getting Started
The MySMSAPio API is a JSON REST API. There are two classes of consumer:
- Client applications — send SMS, generate/verify OTPs, read inbound messages, and query admin stats. Authenticated with a
api_live_…key. - Gateway devices — Android phones that register, send heartbeats, report inbound SMS and delivery receipts. Authenticated with a
gw_live_…key.
Base URL
https://your-host/api/v1
Content-Type
application/json
Authentication
All authenticated endpoints expect an API key in the Authorization header as a Bearer token. Keys are SHA-256 hashed at rest — the raw key is only shown once at creation.
api_live_<64 hex chars>
Permissions-based (send_sms, receive_sms…). Can expire.
gw_live_<64 hex chars>
Bound to a single gateway device. Must be active.
curl https://your-host/api/v1/sms/received \
-H "Authorization: Bearer api_live_..."
Rate Limits
Limits are enforced via Redis and return 429 Too Many Requests with a retry_after hint.
| Resource | Limit | Window |
|---|---|---|
| Send SMS | 100 | per minute / API key |
| Send OTP | 3 | per hour / phone number |
| OTP verify attempts | 3 | per code |
Error Codes
Errors are returned as JSON with an error field. Validation errors also include details.
| Status | Meaning |
|---|---|
| 400 | Bad Request — missing required parameter |
| 401 | Unauthorized — missing or invalid API key |
| 403 | Forbidden — insufficient permissions |
| 404 | Not Found — resource does not exist |
| 409 | Conflict — e.g. gateway already registered |
| 422 | Unprocessable Entity — validation failure |
| 429 | Too Many Requests — rate limit exceeded |
| 500 | Internal Server Error |
/sms/send
Send an SMS
Queues an outbound message. The API returns immediately with a message_id; delivery happens asynchronously via an available gateway. Rate limited to 100/min per API key.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| to | string | yes | E.164 phone number (validated via Phonelib) |
| message | string | yes | Message body (max 1600 chars) |
Example request
-H "Authorization: Bearer api_live_..." \
-H "Content-Type: application/json" \
-d '{"to":"+14155551234","message":"Your code is 482915"}'
Response · 202 Accepted
"success": true,
"message_id": "msg_3f8a9c1b2e7d4f60",
"status": "queued"
}
/sms/status/:message_id
Check SMS Status
Returns the current lifecycle state of a message, including timestamps.
Path parameters
| Parameter | Type | Description |
|---|---|---|
| message_id | string | The ID returned when the message was created |
Response · 200 OK
"message_id": "msg_3f8a9c1b2e7d4f60",
"status": "delivered",
"sent_at": "2026-08-01T12:00:00Z",
"delivered_at": "2026-08-01T12:00:03Z",
"failed_at": null,
"error_message": null
}
Statuses: queued → pending → sent → delivered | failed
/sms/received
List Received SMS
Paginated list of inbound messages, newest first. Optional filters by phone number or date.
Query parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
| phone_number | string | — | Filter by sender phone |
| since | datetime | — | Only messages after this time |
| limit | integer | 50 | Items per page |
Response · 200 OK
"messages": [
{
"message_id": "msg_7a2f...",
"from": "+14155559999",
"message": "STOP",
"received_at": "2026-08-01T12:05:00Z"
}
],
"total": 1,
"page": 1,
"pages": 1
}
/otp/send
Send an OTP
Generates a 6-digit code, sends it via SMS, and returns the expiry time. Max 3 codes per phone per hour.
Request body
| Parameter | Type | Required | Default |
|---|---|---|---|
| phone_number | string | yes | — |
| purpose | string | no | authentication |
| expiry_minutes | integer | no | 5 |
Response · 200 OK
"success": true,
"expires_at": "2026-08-01T12:10:00Z",
"message_id": "msg_9b1c..."
}
/otp/verify
Verify an OTP
Validates the 6-digit code. After 3 failed attempts the code is expired. On success the code is marked verified and cannot be reused.
Request body
| Parameter | Type | Required |
|---|---|---|
| phone_number | string | yes |
| code | string | yes |
Success · 200
"success": true,
"verified": true
}
Failure · 200
"success": false,
"verified": false,
"error": "Invalid or expired OTP",
"attempts_remaining": 2
}
/admin/stats
Admin Statistics
Aggregate system metrics: gateway counts, message throughput, pending/failed counts, and OTP performance for today.
Response · 200 OK
"gateways": { "total": 4, "active": 3, "online": 2, "offline": 2 },
"messages": {
"total_sent": 10234, "total_received": 8821,
"sent_today": 142, "received_today": 98,
"total_today": 240, "pending": 3, "failed_today": 1
},
"otp": { "sent_today": 56, "verified_today": 49, "verification_rate": 87.5 },
"timestamp": "2026-08-01T12:00:00Z"
}
/admin/gateways
List Gateways
Returns every registered gateway device with live status and counters.
/admin/gateways/:id/toggle
Activate or deactivate a gateway. Toggling off stops it from receiving outbound commands.
Toggle response · 200 OK
"success": true,
"gateway": { "id": 1, "device_id": "pixel-001", "active": false }
}
/gateway/register
Register a Gateway
No auth Registers an Android device and returns a fresh gateway API key. Returns 409 if the device_id already exists.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
| device_id | string | yes | Unique device identifier |
| name | string | no | Friendly name (defaults to device id prefix) |
Response · 201 Created
"success": true,
"api_key": "gw_live_a1b2c3...",
"device_id": "pixel-001",
"websocket_url": "wss://your-host/cable"
}
Save api_key immediately — it is never shown again.
/gateway/heartbeat
Gateway Heartbeat
Refreshes the gateway's online status. A gateway is marked offline if no heartbeat arrives within 2 minutes (handled by CheckGatewayHealthJob).
Optional body
| Parameter | Type | Description |
|---|---|---|
| battery_level | integer | 0–100 |
| signal_strength | integer | dBm |
| messages_in_queue | integer | Pending outbound count on device |
Response · 200 OK
/gateway/sms/received
Report Inbound SMS
Called by the gateway when a new SMS arrives on the device. Creates an inbound record and triggers ProcessInboundSmsJob (which fires webhooks).
Request body
| Parameter | Type | Required |
|---|---|---|
| sender | string | yes |
| message | string | yes |
| timestamp | datetime | no |
Response · 200 OK
/gateway/sms/status
Report Delivery Status
Updates an outbound message's status. On failed, RetryFailedSmsJob is enqueued if retries remain (< 3).
Request body
| Parameter | Type | Required | Values |
|---|---|---|---|
| message_id | string | yes | — |
| status | string | yes | sent · delivered · failed |
| error_message | string | no | Reason for failure |
Response · 200 OK
/cable
WebSocket Channel
Gateways maintain a persistent Action Cable connection for real-time command dispatch and status reporting. Connections authenticate with the gateway API key as a query param.
Connect
Server → Gateway commands
"action": "send_sms",
"message_id": "msg_3f8a...",
"recipient": "+14155551234",
"message": "Hello!"
}
Gateway → Server events
| action | Payload |
|---|---|
| heartbeat | battery_level, signal_strength, messages_in_queue |
| delivery_report | message_id, status, error_message |
| message_received | sender, message, timestamp |
Outbound HTTP POST
Webhooks
When configured, the API delivers signed event payloads to your URL via TriggerWebhookJob. If a secret key is set, each request is signed with HMAC-SHA256.
sms_received
Inbound SMS arrived
sms_sent
Message dispatched to gateway
sms_failed
Delivery failed
Example payload (sms_received)
"event": "sms_received",
"message_id": "msg_7a2f...",
"from": "+14155559999",
"message": "Confirm",
"received_at": "2026-08-01T12:05:00Z"
}
Verifying the signature
signature = OpenSSL::HMAC.hexdigest("SHA256", secret_key, payload.to_json)
# Compare with the X-Webhook-Signature header