SMS Preflight API
API Documentation
Automate SMS delivery testing from your CI pipeline, monitoring stack, or any HTTP client. Available on Pro and Team plans.
Base URL: https://api.smsprobe.co.uk/api
Quick start
- Generate an API key in Dashboard → API Keys
- Create a test session via
POST /sessionswith your Sender ID - From your SMS platform, send a message to each
phone_numberin the response - Poll
GET /sessions/:idevery few seconds untilstatusiscompleteorexpired - Read the per-network
verdict—clean,intercepted, orlost
Authentication
All protected endpoints require an API key sent as a Bearer token. Generate keys in your API Keys dashboard. Keys are prefixed sp_ and never expire until revoked.
Authorization: Bearer sp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Type: application/jsonAccount endpoints
/auth/meAPI keyReturns the authenticated user, their active plan, and usage this month.
curl -s https://api.smsprobe.co.uk/api/auth/me \
-H "Authorization: Bearer sp_xxxx"{
"user": {
"id": "019d...",
"name": "Jane Smith",
"email": "jane@example.com",
"plan": "pro",
"company": "Acme Ltd",
"webhook_url": "https://yourapp.com/webhook",
"has_webhook_secret": true
},
"plan": {
"tests_per_month": 100,
"api_access": true,
"webhooks": true,
"history_days": 90
},
"tests_this_month": 12
}API Keys
/api-keysAPI keyList all API keys on your account (names and metadata only — key values are never returned after creation).
curl -s https://api.smsprobe.co.uk/api/api-keys \
-H "Authorization: Bearer sp_xxxx"{
"api_keys": [
{
"id": "019d...",
"name": "Production",
"last_used_at": "2026-04-29T10:00:00Z",
"created_at": "2026-04-01T09:00:00Z"
}
]
}/api-keysAPI keyCreate a new API key. The plain-text key is returned once — store it securely.
curl -s -X POST https://api.smsprobe.co.uk/api/api-keys \
-H "Authorization: Bearer sp_xxxx" \
-H "Content-Type: application/json" \
-d '{"name": "Production"}'| Parameter | Type | Description |
|---|---|---|
namerequired | string | A label for this key (e.g. Production, Staging). Max 100 chars. |
{ "name": "Production" }{
"api_key": {
"id": "019d...",
"name": "Production",
"key": "sp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"created_at": "2026-04-29T09:00:00Z"
},
"message": "Store this key securely - it will not be shown again."
}/api-keys/:idAPI keyRevoke an API key immediately. Any requests using it will return 401.
curl -s -X DELETE https://api.smsprobe.co.uk/api/api-keys/019d... \
-H "Authorization: Bearer sp_xxxx"{ "message": "API key revoked" }Networks
/networksList all monitored UK networks and their current health. No authentication required.
curl -s https://api.smsprobe.co.uk/api/networks{
"networks": [
{
"id": "...",
"name": "EE",
"slug": "ee",
"is_active": true,
"status": "live",
"last_heartbeat_at": "2026-04-29T12:00:00Z",
"uptime_24h": 100,
"avg_latency_ms": 312,
"signal_bars": 4,
"signal_dbm": -88,
"network_type": "LTE",
"recent_heartbeats": [
{ "success": true, "latency_ms": 310, "signal_dbm": -88, "at": "2026-04-29T11:58:00Z" },
...
]
},
...
]
}Test Sessions
/sessionsAPI keyCreate a new test session. Returns a phone number for each active network — send your SMS to those numbers from your platform. Sessions expire after 5 minutes.
curl -s -X POST https://api.smsprobe.co.uk/api/sessions \
-H "Authorization: Bearer sp_xxxx" \
-H "Content-Type: application/json" \
-d '{"sender_id": "MYAPP", "expected_body": "Hello!"}'| Parameter | Type | Description |
|---|---|---|
sender_idrequired | string | The Sender ID or phone number your platform sends SMS from (max 20 chars) |
expected_body | string | The exact message body you will send — enables body-match detection and the intercepted verdict |
client_ref | string | Your own reference string, echoed in results and webhook payloads (max 255 chars) |
webhook_url | string | HTTPS URL to receive the webhook for this session only — overrides your account-level webhook URL |
{
"sender_id": "MYAPP",
"expected_body": "Hello!",
"client_ref": "deploy-42",
"webhook_url": "https://yourapp.com/webhook"
}{
"id": "019d...",
"test_code": "A1B2C3D4",
"status": "pending",
"sender_id": "MYAPP",
"expected_body": "Hello!",
"client_ref": "deploy-42",
"webhook_url": null,
"expires_at": "2026-04-29T12:05:00Z",
"created_at": "2026-04-29T12:00:00Z",
"results": [
{
"network": "ee",
"network_name": "EE",
"phone_number": "+447...",
"status": "pending",
"verdict": null,
"latency_ms": null,
"sender_received": null,
"body_received": null,
"body_matched": null,
"received_at": null
},
...
]
}/sessions/:idAPI keyFetch a session and its live per-network results. Poll every 3–5 seconds while status is pending.
curl -s https://api.smsprobe.co.uk/api/sessions/019d... \
-H "Authorization: Bearer sp_xxxx"{
"id": "019d...",
"status": "complete",
"sender_id": "MYAPP",
"results": [
{
"network": "ee",
"network_name": "EE",
"phone_number": "+447...",
"status": "received",
"verdict": "clean",
"latency_ms": 4200,
"sender_received": "MYAPP",
"body_received": "Hello!",
"body_matched": true,
"received_at": "2026-04-29T12:00:04Z"
},
{
"network": "vodafone",
"network_name": "Vodafone",
"phone_number": "+447...",
"status": "received",
"verdict": "intercepted",
"latency_ms": 6100,
"sender_received": "447...",
"body_received": "Hello!",
"body_matched": true,
"received_at": "2026-04-29T12:00:06Z"
},
...
]
}/sessionsAPI keyList your test sessions newest-first. Filter by status using filter[status]=pending|complete|expired. Paginated, 20 per page.
curl -s "https://api.smsprobe.co.uk/api/sessions?filter[status]=complete&page=1" \
-H "Authorization: Bearer sp_xxxx"| Parameter | Type | Description |
|---|---|---|
filter[status] | string | Filter by status: pending, complete, or expired |
page | integer | Page number (default: 1, 20 results per page) |
{
"data": [ { "id": "...", "status": "complete", "sender_id": "MYAPP", ... }, ... ],
"meta": { "current_page": 1, "last_page": 3, "per_page": 20, "total": 48 }
}Idempotency
Pass an Idempotency-Key: <uuid> header on POST /sessions to safely retry without creating duplicate sessions.
Verdicts
Once a result is received, a verdict is assigned to each network result:
| Verdict | Meaning |
|---|---|
| clean | SMS received with the original Sender ID intact |
| intercepted | SMS received but the Sender ID was altered by the network |
| lost | Session expired with no SMS received on this network |
| null | Result still pending — session has not yet expired |
Webhooks
Configure a webhook URL in Dashboard → Webhooks to receive a POST when each session completes. You can also override the URL per-session via the webhook_url field on POST /sessions.
Request headers sent to your endpoint
Content-Type: application/json
User-Agent: SMSProbe-Webhook/1.0
X-SMSProbe-Event: session.complete
X-SMSProbe-Signature: sha256=<hmac-hex>
X-SMSProbe-Delivery: <delivery-id>Retry policy
Retries occur on connection errors or any 5xx response. A 2xx response marks the delivery as successful. 10 second request timeout.
Payload
{
"event": "session.complete",
"session": {
"id": "019d...",
"status": "complete",
"sender_id": "MYAPP",
"client_ref": "deploy-42",
"results": [
{ "network": "ee", "verdict": "clean", "latency_ms": 4200, ... },
{ "network": "vodafone", "verdict": "intercepted", ... },
...
]
}
}Signature verification
The X-SMSProbe-Signature header is formatted as sha256=<hex>. Verify it using your signing secret from Dashboard → Webhooks. Always reject requests where the header is missing or the comparison fails — never make verification optional.
const crypto = require('crypto');
function verifySignature(secret, rawBody, signatureHeader) {
if (!signatureHeader) return false;
// Strip the "sha256=" prefix before comparing
const sigHex = signatureHeader.replace(/^sha256=/, '');
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(sigHex);
// timingSafeEqual throws if lengths differ — check first
return a.length === b.length && crypto.timingSafeEqual(a, b);
}Errors
All errors return a JSON body. Validation errors include a per-field errors map:
401 — Unauthenticated
{ "message": "Unauthenticated." }422 — Validation error
{
"message": "The sender id field is required.",
"errors": {
"sender_id": ["The sender id field is required."]
}
}422 — Quota exceeded
{
"message": "Monthly test quota exceeded. Upgrade your plan to run more tests."
}Plans & limits
| Plan | Tests / month | API access | Webhooks | History |
|---|---|---|---|---|
| Free | 5 | — | — | 30 days |
| Pro | 100 | ✓ | ✓ | 90 days |
| Team | Unlimited | ✓ | ✓ | 365 days |
HTTP status codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Resource created |
| 401 | Missing or invalid API key |
| 403 | Forbidden — resource belongs to another account |
| 422 | Validation error or monthly quota exceeded |
| 500 | Server error |