Relay API
Developer Documentation • v1
Getting Started
Follow these steps to start sending SMS programmatically via the Relay API.
1. Enable API Access
API access must be enabled by an administrator. Contact your account admin to enable the Developer API for your account.
2. Get Your API Key
Once API access is enabled, you can find your API key in the dashboard.
3. Configure Webhooks (Optional)
Set up webhook URLs to receive real-time notifications for message delivery status and inbound messages.
4. Send Your First SMS
Use the API to send an SMS message. The message will be queued and sent asynchronously.
Finding Your API Key
Via Dashboard (UI)
- Log in to your Relay dashboard at relay-sms.app
- Go to Settings → API tab (relay-sms.app/dash/settings?tab=api)
- Your API key will be displayed (masked for security)
- Click the copy button to copy the full key
- To regenerate your key, click "Regenerate API Key" (this invalidates the old key immediately)
Configuring Webhooks via Dashboard
Via Dashboard (UI)
- Go to relay-sms.app/dash/settings?tab=api
- Scroll to the "Webhook Configuration" section
- Enter your Status Webhook URL to receive delivery notifications
- Enter your Inbound Webhook URL to receive incoming SMS notifications
- Click "Save" to apply your changes
Configuring Webhooks via API
Via API
curl -X PUT https://api.frankdu.co/api/v1/webhooks \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"status_url": "https://your-server.com/webhooks/status",
"inbound_url": "https://your-server.com/webhooks/inbound"
}'Testing Webhooks
Use our webhook test bins to verify your integration. Send test payloads to /api/v1/status/bin or /api/v1/messages/bin, then retrieve them with a GET request to inspect the data.
# Send a test status webhook
curl -X POST https://api.frankdu.co/api/v1/status/bin \
-H "Content-Type: application/json" \
-d '{"test": "status payload"}'
# Retrieve logged webhooks (requires auth)
curl https://api.frankdu.co/api/v1/status/bin \
-H "X-API-Key: your_api_key"Base URL
https://api.frankdu.co/api/v1Authentication
All API requests require authentication via API key. Include your key in the request header.
Header Authentication
X-API-Key: your_api_key_hereBearer Token
Authorization: Bearer your_api_key_hereEndpoints
/sms/sendSend SMS
Send an SMS message asynchronously. Returns immediately with `id` and `client_reference_id` for tracking.
Request
| Parameters | Type | Description |
|---|---|---|
toRequired | string | Recipient phone number in E.164 format |
fromRequired | string | Sender phone number (must be owned by you) |
bodyRequired | string | Message content (max 1600 characters) |
status_callback | string | URL to receive status updates for this message |
metadata | object | Custom metadata returned in webhooks |
Example
curl -X POST https://api.frankdu.co/api/v1/sms/send \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"to": "+1234567890",
"from": "+1987654321",
"body": "Hello from Relay!",
"metadata": {"order_id": "12345"}
}'Response (202)
{
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"client_reference_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "accepted",
"to": "+1234567890",
"from": "+1987654321",
"created_at": "2026-01-27T07:00:00Z",
"message": "Message queued for delivery. Poll GET /sms/{id} for status updates."
},
"timestamp": "2026-01-27T07:00:00Z",
"unix_timestamp": 1769497200000
}/sms/{id}Get Message
Retrieve status/details of a specific message. Query by `client_reference_id` is recommended.
Response
{
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"client_reference_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "delivered",
"to": "+1234567890",
"from": "+1987654321",
"body": "Hello from Relay!",
"direction": "outbound",
"segments": "1",
"created_at": "2026-01-27T07:00:00Z",
"delivered_at": "2026-01-27T07:00:02Z",
"metadata": {"order_id": "12345"}
},
"timestamp": "2026-01-27T07:00:02Z",
"unix_timestamp": 1769497202000
}/messagesList Messages
List all messages for the authenticated user with pagination support, including `client_reference_id`.
| Parameters | Type | Description |
|---|---|---|
limit | integer | Number of results per page (max 100) |
page | integer | Page number for pagination |
direction | string | Filter by direction (inbound/outbound) |
status | string | Filter by status (delivered, failed, etc.) |
Response
{
"data": {
"messages": [
{
"id": "SM...",
"client_reference_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "delivered",
"to": "+1234567890",
"from": "+1987654321",
"body": "Hello!",
"direction": "outbound",
"created_at": "2026-01-27T07:00:00Z"
}
],
"page": 1,
"limit": 25,
"has_more": true
},
"timestamp": "2026-01-27T07:00:02Z",
"unix_timestamp": 1769497202000
}/messages/{id}/logGet Message Log
Get the delivery timeline for a message showing status progression and `client_reference_id` mapping.
Response
{
"data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"client_reference_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "delivered",
"direction": "outbound",
"to": "+1234567890",
"from": "+1987654321",
"segments": "1",
"timeline": [
{"status": "accepted", "timestamp": "2026-01-27T07:00:00Z"},
{"status": "sent", "timestamp": "2026-01-27T07:00:01Z"},
{"status": "delivered", "timestamp": "2026-01-27T07:00:02Z"}
]
},
"timestamp": "2026-01-27T07:00:02Z",
"unix_timestamp": 1769497202000
}/numbersList Numbers
List all phone numbers owned by the authenticated user.
Response
{
"data": {
"numbers": [
{
"phone": "+1987654321",
"created_at": "2026-01-01T00:00:00Z",
"inbound_callback": null
}
],
"total": 1
},
"timestamp": "2026-01-27T07:00:02Z",
"unix_timestamp": 1769497202000
}/numbers/{number}/spam-diagnosisSpam Diagnosis
Diagnose spam filtering risk for an owned sender number using channel delivery signals (latest outbound only, no message content exposure).
| Parameters | Type | Description |
|---|---|---|
number (path)Required | string | Owned sender number in E.164 format (path parameter) |
hours | integer | Lookback window in hours (default 72, max 336) |
limit | integer | Number of results per page (max 100) |
max_pages | integer | Maximum pages to scan (default 5, max 20) |
Example
curl -X GET "https://api.frankdu.co/api/v1/numbers/%2B18882034193/spam-diagnosis?hours=72&limit=50&max_pages=5" \
-H "X-API-Key: your_api_key"Response (200)
{
"data": {
"number": "+18882034193",
"spam_filtered": true,
"diagnosis_reason": "provider_spam_signal_detected",
"delivery_status": "delivery_failed",
"message_content_included": false,
"checked_messages": 2,
"scanned_pages": 1,
"window_hours": 72,
"latest_message": {
"status": "delivery_failed",
"created_at": "2026-03-11T10:00:33Z",
"updated_at": "2026-03-11T10:00:35Z"
}
},
"timestamp": "2026-03-11T10:31:11Z",
"unix_timestamp": 1773225071333
}Response (404)
{
"error": {
"code": "NOT_FOUND",
"message": "No outbound message found for this number in the requested window"
},
"timestamp": "2026-03-11T10:32:22Z",
"unix_timestamp": 1773225142764
}/balanceGet Balance
Get the current account balance.
Response
{
"data": {
"balance": 125.50,
"currency": "USD"
},
"timestamp": "2026-01-27T07:00:02Z",
"unix_timestamp": 1769497202000
}/transactionsList Transactions
List transaction history with pagination support.
| Parameters | Type | Description |
|---|---|---|
limit | integer | Number of results per page (max 100) |
page | integer | Page number for pagination |
Response
{
"data": {
"transactions": [
{
"timestamp": 1706338800,
"type": "debit",
"amount": 0.015,
"description": "SMS outbound delivered (US)",
"balance_after": 125.485
}
],
"page": 1,
"limit": 25,
"total": 1000,
"has_more": true
},
"timestamp": "2026-01-27T07:00:02Z",
"unix_timestamp": 1769497202000
}/webhooksGet Webhooks
Get the global webhook configuration.
Response
{
"data": {
"status_url": "https://example.com/webhooks/status",
"inbound_url": "https://example.com/webhooks/inbound"
},
"timestamp": "2026-01-27T07:00:02Z",
"unix_timestamp": 1769497202000
}/webhooksSet Webhooks
Configure global webhook URLs for status and inbound notifications.
| Parameters | Type | Description |
|---|---|---|
status_url | string | URL for status change notifications |
inbound_url | string | URL for inbound message notifications |
Example
curl -X PUT https://api.frankdu.co/api/v1/webhooks \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"status_url": "https://example.com/webhooks/status",
"inbound_url": "https://example.com/webhooks/inbound"
}'/numbers/{phone}/webhooksGet Number Webhooks
Get the webhook configuration for a specific phone number.
Response
{
"data": {
"phone": "+1987654321",
"inbound_callback": "https://example.com/webhooks/inbound/+1987654321"
},
"timestamp": "2026-01-27T07:00:02Z",
"unix_timestamp": 1769497202000
}/numbers/{phone}/webhooksSet Number Webhooks
Configure an inbound webhook for a specific phone number.
| Parameters | Type | Description |
|---|---|---|
inbound_callback | string | URL for inbound messages on this number |
/api-key/regenerateRegenerate API Key
Generate a new API key, immediately invalidating the current one.
Response
{
"data": {
"api_key": "new_api_key_here",
"message": "API key regenerated. Save this key - it will not be shown again."
},
"timestamp": "2026-01-27T07:00:02Z",
"unix_timestamp": 1769497202000
}/sms/batchSend Batch SMS
Send multiple SMS messages with per-recipient customized content. Max 1000 messages per batch. Accepted results return `client_reference_id`.
Request
| Parameters | Type | Description |
|---|---|---|
fromRequired | string | Sender phone number (must be owned by you) |
messagesRequired | array | Array of message objects with to and body fields |
messages[].toRequired | string | Recipient phone number in E.164 format |
messages[].bodyRequired | string | Message body for this recipient (max 1600 chars) |
messages[].metadata | object | Custom metadata returned in webhooks |
messages[].status_callback | string | URL to receive status updates for this message |
Example
curl -X POST https://api.frankdu.co/api/v1/sms/batch \
-H "X-API-Key: your_api_key" \
-H "Content-Type: application/json" \
-d '{
"from": "+1987654321",
"messages": [
{"to": "+1234567890", "body": "Hello Alice!"},
{"to": "+1234567891", "body": "Hello Bob!"},
{"to": "+1234567892", "body": "Hello Charlie!"}
]
}'Response (202)
{
"data": {
"batch_id": "batch_abc123def456",
"accepted": 3,
"rejected": 0,
"total": 3,
"results": [
{"index": 0, "to": "+1234567890", "client_reference_id": "msg_uuid_1", "status": "accepted"},
{"index": 1, "to": "+1234567891", "client_reference_id": "msg_uuid_2", "status": "accepted"},
{"index": 2, "to": "+1234567892", "client_reference_id": "msg_uuid_3", "status": "accepted"}
]
},
"timestamp": "2026-01-27T07:00:02Z",
"unix_timestamp": 1769497202000
}/sms/batch/{batch_id}Get Batch Status
Get the status and progress of a batch SMS job.
Response
{
"data": {
"job_id": "batch_abc123def456",
"status": "completed",
"total": 3,
"processed": 3,
"completed": 3,
"failed": 0,
"from_number": "+1987654321",
"channel": "api",
"created_at": "2026-01-27T07:00:00Z"
},
"timestamp": "2026-01-27T07:00:02Z",
"unix_timestamp": 1769497202000
}/sms/batch/{batch_id}/messagesList Batch Messages
List all messages in a batch with pagination support, including `id` and `client_reference_id`.
| Parameters | Type | Description |
|---|---|---|
page | integer | Page number for pagination |
Response
{
"data": {
"items": [
{
"index": 0,
"id": "msg_uuid_1",
"client_reference_id": "msg_uuid_1",
"to": "+1234567890",
"status": "delivered",
"created_at": "2026-01-27T07:00:00Z"
},
{
"index": 1,
"id": "msg_uuid_2",
"client_reference_id": "msg_uuid_2",
"to": "+1234567891",
"status": "delivered",
"created_at": "2026-01-27T07:00:00Z"
}
],
"page": 1,
"limit": 50,
"total": 3,
"has_more": false
},
"timestamp": "2026-01-27T07:00:02Z",
"unix_timestamp": 1769497202000
}Webhooks
Configure webhooks to receive real-time notifications for message status changes and inbound messages.
Status Webhook
Triggered when message status changes (queued, sent, delivered, failed, undelivered). `client_reference_id` is your stable business key, while `message_id` is provider/canonical ID for transport tracing. Includes `timestamp` (UTC Z) and `unix_timestamp` (ms).
Payload
{
"event": "message.status",
"message_id": "SM1234567890abcdef",
"client_reference_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "delivered",
"to": "+1234567890",
"from": "+1987654321",
"direction": "outbound",
"timestamp": "2026-01-27T07:00:02Z",
"unix_timestamp": 1769497202000,
"error_code": null,
"error_message": null,
"metadata": {"order_id": "12345"}
}Inbound Webhook
Triggered when an SMS is received on your phone number. Includes `timestamp` (UTC Z) and `unix_timestamp` (ms).
Payload
{
"event": "message.inbound",
"message_id": "SM1234567890abcdef",
"from": "+1234567890",
"to": "+1987654321",
"body": "Hello!",
"timestamp": "2026-01-27T07:00:00Z",
"unix_timestamp": 1769497200000,
"media_urls": []
}Error Codes
| Code | HTTP | Description |
|---|---|---|
UNAUTHORIZED | 401 | Invalid or missing API key |
FORBIDDEN | 403 | API access not enabled or unauthorized |
NOT_FOUND | 404 | Resource not found |
INVALID_REQUEST | 400 | Invalid request parameters |
INSUFFICIENT_BALANCE | 402 | Account balance too low |
QUEUE_FAILED | 503 | Failed to queue message |
INTERNAL_ERROR | 500 | Internal server error |