Relay API

Developer Documentationv1

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)

  1. Log in to your Relay dashboard at relay-sms.app
  2. Go to Settings → API tab (relay-sms.app/dash/settings?tab=api)
  3. Your API key will be displayed (masked for security)
  4. Click the copy button to copy the full key
  5. To regenerate your key, click "Regenerate API Key" (this invalidates the old key immediately)

Configuring Webhooks via Dashboard

Via Dashboard (UI)

  1. Go to relay-sms.app/dash/settings?tab=api
  2. Scroll to the "Webhook Configuration" section
  3. Enter your Status Webhook URL to receive delivery notifications
  4. Enter your Inbound Webhook URL to receive incoming SMS notifications
  5. 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/v1

Authentication

All API requests require authentication via API key. Include your key in the request header.

Header Authentication

X-API-Key: your_api_key_here

Bearer Token

Authorization: Bearer your_api_key_here

Endpoints

POST/sms/send

Send SMS

Send an SMS message asynchronously. Returns immediately with `id` and `client_reference_id` for tracking.

Request

ParametersTypeDescription
toRequiredstringRecipient phone number in E.164 format
fromRequiredstringSender phone number (must be owned by you)
bodyRequiredstringMessage content (max 1600 characters)
status_callbackstringURL to receive status updates for this message
metadataobjectCustom 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
}
GET/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
}
GET/messages

List Messages

List all messages for the authenticated user with pagination support, including `client_reference_id`.

ParametersTypeDescription
limitintegerNumber of results per page (max 100)
pageintegerPage number for pagination
directionstringFilter by direction (inbound/outbound)
statusstringFilter 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
}
GET/messages/{id}/log

Get 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
}
GET/numbers

List 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
}
GET/numbers/{number}/spam-diagnosis

Spam Diagnosis

Diagnose spam filtering risk for an owned sender number using channel delivery signals (latest outbound only, no message content exposure).

ParametersTypeDescription
number (path)RequiredstringOwned sender number in E.164 format (path parameter)
hoursintegerLookback window in hours (default 72, max 336)
limitintegerNumber of results per page (max 100)
max_pagesintegerMaximum 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
}
GET/balance

Get Balance

Get the current account balance.

Response

{
  "data": {
    "balance": 125.50,
    "currency": "USD"
  },
  "timestamp": "2026-01-27T07:00:02Z",
  "unix_timestamp": 1769497202000
}
GET/transactions

List Transactions

List transaction history with pagination support.

ParametersTypeDescription
limitintegerNumber of results per page (max 100)
pageintegerPage 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
}
GET/webhooks

Get 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
}
PUT/webhooks

Set Webhooks

Configure global webhook URLs for status and inbound notifications.

ParametersTypeDescription
status_urlstringURL for status change notifications
inbound_urlstringURL 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"
  }'
GET/numbers/{phone}/webhooks

Get 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
}
PUT/numbers/{phone}/webhooks

Set Number Webhooks

Configure an inbound webhook for a specific phone number.

ParametersTypeDescription
inbound_callbackstringURL for inbound messages on this number
POST/api-key/regenerate

Regenerate 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
}
POST/sms/batch

Send Batch SMS

Send multiple SMS messages with per-recipient customized content. Max 1000 messages per batch. Accepted results return `client_reference_id`.

Request

ParametersTypeDescription
fromRequiredstringSender phone number (must be owned by you)
messagesRequiredarrayArray of message objects with to and body fields
messages[].toRequiredstringRecipient phone number in E.164 format
messages[].bodyRequiredstringMessage body for this recipient (max 1600 chars)
messages[].metadataobjectCustom metadata returned in webhooks
messages[].status_callbackstringURL 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
}
GET/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
}
GET/sms/batch/{batch_id}/messages

List Batch Messages

List all messages in a batch with pagination support, including `id` and `client_reference_id`.

ParametersTypeDescription
pageintegerPage 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

CodeHTTPDescription
UNAUTHORIZED401Invalid or missing API key
FORBIDDEN403API access not enabled or unauthorized
NOT_FOUND404Resource not found
INVALID_REQUEST400Invalid request parameters
INSUFFICIENT_BALANCE402Account balance too low
QUEUE_FAILED503Failed to queue message
INTERNAL_ERROR500Internal server error

Relay API v1 • © 2026 Relay