The SMS API, written for engineers.

REST + JSON · x-api-key auth · two-tier DLR callbacks

One POST sends the message. Two callbacks tell you it was submitted and then that it was delivered — we report those separately because they were never the same thing. OFCA Sender IDs, DNC filtering and the per-region field differences are all spelled out below.

After the product overview instead? See the SMS service page — 100+ HK securities firms, dual-route failover, # Sender ID handled for you.

POST /v1/sms/send
x-api-key: YOUR_API_KEY

{
  "sender": "XLEAD",
  "country_code": "852",
  "mobile": "91234567",
  "content": "Your login code is 482910"
}

→ { "rc": 100, "ref_id": "145737475" }
→ callback 1: "status": "SMD"
→ callback 2: "status": "DELIVRD"
Quickstart

One POST and your first SMS is on its way

No SDK, no OAuth dance. Grab an API key, paste the snippet below, change the number, run it.

  1. 1

    Get an API key

    Create one in the X Lead dashboard. Every request carries it in the x-api-key header.

  2. 2

    Call the send endpoint

    POST one JSON body — one request per recipient. The response returns a ref_id immediately.

  3. 3

    Receive callbacks

    Set a Callback URL in the dashboard and we push submission plus carrier delivery status back to you.

Send one Hong Kong SMS

curl -X POST https://api.connect.xleadfunnel.com/v1/sms/send \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "sender": "XLEAD",
    "country_code": "852",
    "mobile": "91234567",
    "content": "Your login code is 482910",
    "send_also_ofca_registrants": false
  }'

Synchronous response

{
  "rc": 100,
  "rm": "OK",
  "ref_id": "145737475"
}

ref_id identifies this request — both callbacks carry the same ref_id, so use it to reconcile.

API reference

Authentication, endpoints and parameters

One header handles auth; three endpoints cover the whole send chain: send, usage, balance.

Authentication

Base URL
https://api.connect.xleadfunnel.com
Auth
Header: x-api-key: YOUR_API_KEY
Encoding
UTF-8
Request format
application/json (POST)

Endpoints

  • POST /v1/sms/send Send one SMS — one request per recipient.
  • GET /v1/sms/get-usage-report Usage summary for a date range (date_from / date_to, YYYY-MM-DD).
  • GET /v1/acct/get-points-balance Remaining points balance. SMS and MMS draw on the same pool.

POST /v1/sms/send parameters

ParameterTypeRequiredDescription
country_code string Required Region dialling code, 1–3 digits, no leading zeros. Hong Kong is 852.
mobile string Required Recipient's mobile number, up to 10 digits, a leading zero is accepted.
content string Optional Message body, UTF-8.
sender string Optional Sender name, 3–11 alphanumeric characters. Hong Kong requires a pre-registered Sender ID; Taiwan does not take this field.
send_also_ofca_registrants boolean Optional Defaults to false — do not send to numbers on the OFCA Do-Not-Call register. Hong Kong only.

Response fields

Parameter Type Description
rc integer 100 = OK; -100 = invalid input
rm string Return message, carries the reason on failure
ref_id string Identifier for this request; both callbacks echo it back

Region differences: one field, two rules

Whether `sender` is required is decided by the destination regulator — which is why one hard-coded payload will not work across regions.

+852 · Hong Kong

A pre-registered Sender ID is mandatory or the request is rejected. send_also_ofca_registrants controls whether the OFCA DNC register is bypassed.

+886 · Taiwan

No sender field needed. Leave it out — supplying one can cause validation to fail.

DLR / callbacks

Two tiers of reporting — submission first, delivery second

"Submitted" is not "delivered". We split them into two separate callbacks so you can tell a bad number on your side from a failure at the carrier.

Set a Callback URL in your dashboard first — otherwise none of these fire.

Callback payload

rc
Return code
rm
Return message with detail, where available
ref_id
The reference id we returned on your original API call
status
Submission or delivery status of the request

Example callbacks

// 1st — submitted to carrier, this one is charged
{ "rc": 100, "rm": "OK", "ref_id": "145737475", "status": "SMD" }

// 1st — validation failed, not charged
{ "rc": -100, "rm": "...", "ref_id": "145737476", "status": "IM" }

// 2nd — carrier reported the final status
{ "rc": 100, "rm": "OK", "ref_id": "145737475", "status": "DELIVRD" }
1

1st callback · send request report

Whether your request passed validation and reached the carrier. This tier decides whether you are charged.

rc = 100 message will be charged
SMD
A valid send — message submitted to the carrier for delivery
rc = -100 message will not be charged
IM
Incorrect country code or mobile number
OFCA
On the OFCA DNC register and filtered (Hong Kong only)
IUL
On the unsend list — message did not go out
IP
Insufficient points
IS
Invalid sender, or the Sender ID is not registered on your account
2

2nd callback · delivery report (DLR)

When the carrier reports the final handset status, we relay it to you unchanged.

rc = 100
DELIVRD
Carrier delivered to the recipient device
EXPIRED
Not delivered — processing exceeded the validity period
UNDELIV
Undelivered — unreachable destination, network issue, or device switched off
REJECTD
Rejected — invalid content, blacklisted, or policy restriction
UNKNWN
The carrier cannot determine the final delivery status

Why we publish every failure code

Some providers return "submitted" and bill you regardless. We split submission from delivery and pass each failure code through untouched because you need to reconcile: requests accepted, requests submitted, messages actually delivered — all three numbers have to line up.

How Honest Delivery works →
Integration

Wire it up in whatever you already use

The same POST, six ways. Each one uses the language's standard HTTP client — no SDK of ours to install.

cURL

curl -X POST https://api.connect.xleadfunnel.com/v1/sms/send \
  -H "Content-Type: application/json" \
  -H "x-api-key: $XLEAD_API_KEY" \
  -d '{
    "sender": "XLEAD",
    "country_code": "852",
    "mobile": "91234567",
    "content": "Your login code is 482910"
  }'

Node.js

const res = await fetch('https://api.connect.xleadfunnel.com/v1/sms/send', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'x-api-key': process.env.XLEAD_API_KEY,
  },
  body: JSON.stringify({
    sender: 'XLEAD',
    country_code: '852',
    mobile: '91234567',
    content: 'Your login code is 482910',
  }),
});

const { rc, rm, ref_id } = await res.json();
if (rc !== 100) throw new Error(`send failed: ${rm}`);
console.log('queued as', ref_id);

Python

import os, requests

res = requests.post(
    'https://api.connect.xleadfunnel.com/v1/sms/send',
    headers={'x-api-key': os.environ['XLEAD_API_KEY']},
    json={
        'sender': 'XLEAD',
        'country_code': '852',
        'mobile': '91234567',
        'content': 'Your login code is 482910',
    },
    timeout=10,
)

data = res.json()
if data['rc'] != 100:
    raise RuntimeError(f"send failed: {data['rm']}")
print('queued as', data['ref_id'])

PHP

<?php
$ch = curl_init('https://api.connect.xleadfunnel.com/v1/sms/send');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        'x-api-key: ' . getenv('XLEAD_API_KEY'),
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'sender'       => 'XLEAD',
        'country_code' => '852',
        'mobile'       => '91234567',
        'content'      => 'Your login code is 482910',
    ], JSON_UNESCAPED_UNICODE),
]);

$data = json_decode(curl_exec($ch), true);
curl_close($ch);

if ($data['rc'] !== 100) {
    throw new RuntimeException('send failed: ' . $data['rm']);
}
echo 'queued as ' . $data['ref_id'];

Java

var body = """
    {"sender":"XLEAD","country_code":"852",
     "mobile":"91234567","content":"Your login code is 482910"}
    """;

var req = HttpRequest.newBuilder()
    .uri(URI.create("https://api.connect.xleadfunnel.com/v1/sms/send"))
    .header("Content-Type", "application/json")
    .header("x-api-key", System.getenv("XLEAD_API_KEY"))
    .POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
    .build();

var res = HttpClient.newHttpClient()
    .send(req, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));

System.out.println(res.body());   // {"rc":100,"rm":"OK","ref_id":"..."}

C# / .NET

using var http = new HttpClient();
http.DefaultRequestHeaders.Add(
    "x-api-key", Environment.GetEnvironmentVariable("XLEAD_API_KEY"));

var payload = new {
    sender       = "XLEAD",
    country_code = "852",
    mobile       = "91234567",
    content      = "Your login code is 482910"
};

var res = await http.PostAsJsonAsync("https://api.connect.xleadfunnel.com/v1/sms/send", payload);
var data = await res.Content.ReadFromJsonAsync<JsonElement>();

if (data.GetProperty("rc").GetInt32() != 100)
    throw new Exception(data.GetProperty("rm").GetString());

In production, read the API key from an environment variable rather than hard-coding it.

Getting started

3 steps from account to production

No long contract before you can try it. Most clients have a test send working in the first week, with Sender ID registration running in the background.

  1. 1

    Open an account

    Tell us the use case and expected volume; we open the account and configure routes suited to your destinations.

  2. 2

    Create an API key

    Generate the key in the dashboard, then send it on every request in the x-api-key header. Server-side only.

  3. 3

    Set up the API and test in 3 minutes

    Send a one-click test from the dashboard to confirm routing and presentation, then follow the Quickstart above to send your first message through the API — about 3 minutes in total.

Once you are sending, follow up with

  • +

    Set a Callback URL (optional)

    When you want the two-tier DLR callbacks, enter your endpoint in the dashboard — without it no DLRs reach you.

  • +

    Register a # Sender ID (for your brand)

    Sending to Hong Kong requires a registered sender name — it is also your brand in the inbox. We handle the naming checks, documents and secure network; this runs in parallel with your integration.

Want a test key first?

If you need to run integration tests against the API, contact us for a test API key and a small allocation of test points — no contract required first.

Request a test key →

Integration FAQ

Can one request go to multiple recipients? +

No. /v1/sms/send is one request per recipient. Loop on your side for a broadcast — each send returns its own ref_id, so reconciliation and retries stay per-message. For large scheduled broadcasts the dashboard sender is a better fit than a hand-rolled loop.

Does rc = 100 mean the customer received it? +

No. The synchronous rc = 100 only means we accepted and queued the request. Submission is confirmed by the 1st callback (status = SMD means it reached the carrier); actual arrival on the handset is the 2nd callback's DELIVRD. Conflating these three is the single most common reporting bug we see.

Why is sender required for Hong Kong but not Taiwan? +

Different rules. Hong Kong's OFCA runs a sender-registration scheme, so commercial messages must use a registered sender name — sender is mandatory and must be a Sender ID registered on your account. Taiwan has no equivalent scheme and does not take the field. Build the payload per destination region.

What should send_also_ofca_registrants be set to? +

It defaults to false, meaning numbers on the OFCA Do-Not-Call register are not sent to — marketing traffic should keep that default. Only consider true where the message falls outside what the register governs (for example a transactional notice under an existing relationship with the recipient), and the responsibility for that call sits with the sender. When unsure, leave it false.

My callbacks never arrive — what do I check? +

In order: is a Callback URL saved in the dashboard; is that URL reachable from the public internet and returning 2xx; is it HTTPS with a valid certificate; is a firewall dropping it. Also make your handler idempotent — key on ref_id + status so a redelivered callback does not double-decrement stock or double-send an email.

How should I handle the non-DELIVRD statuses? +

UNDELIV is usually a powered-off or out-of-coverage handset — retry later. EXPIRED means it never got through within the validity window; confirm the number is still live before retrying. REJECTD is a content or policy problem — resending the same body will just be rejected again, so fix the content or check the sender setup. UNKNWN means the carrier could not determine the outcome; do not treat it as an outright failure and blindly resend — track the rate first.

How do I reconcile my numbers? +

Use ref_id to join three counts: requests you sent, 1st callbacks that returned SMD (this is the billing basis), and 2nd callbacks that returned DELIVRD. /v1/sms/get-usage-report returns sms_count and points_used for a date range so you can cross-check against your own records.

Is there a test environment? +

Once your account is open, an admin can fire a one-click test send from the dashboard to confirm the Sender ID and routing before any code is written. For API-level integration testing, contact us for a test API key and a small allocation of test points.

Ready to get started?

Contact us — send soon after a quick account setup. Free trial credit lets you integrate first and confirm the platform fits your use case.