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: "delivery_status": "SS"
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

    Apply for one in the X Lead dashboard. Once it is activated, 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
  }'

Send one Taiwan SMS (+886)

Taiwan takes no sender and no send_also_ofca_registrants; every other field is the same.

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

Taiwan SMS API quickstart: from sign-up to your first SMS to +886 →

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.

Taiwan +886

Taiwan SMS API: integrate first, compliance next

Hong Kong steps stay in Quickstart and Getting started above. For Taiwan numbers the payload differs: no sender field in the API, and plain-text OTP to your own phone is usually enough for an integration test.

  1. 1

    Open an account

    Sign up online for trial credit — no credit card.

  2. 2

    Apply for an API key

    Request one in the dashboard; call the send endpoint with x-api-key once it is active.

  3. 3

    Send your first +886

    Set country_code to 886 and mobile to a Taiwan number; start with plain-text OTP content.

  4. 4

    Before production

    Messages with URLs or phone numbers need Taiwan commercial-SMS white-list registration; marketing copy must meet local labelling rules.

Step-by-step: sign-up to your first +886 SMS → Sender labelling and sample copy → Sign up free and apply for a key →

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; Taiwan is 886.
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
1st callback: submission status of the request
delivery_status
2nd callback: SS = delivered, SF = not delivered
desc_code
Present only when delivery_status = SF — the reason it was not delivered

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 delivered
{ "rc": 100, "rm": "...", "ref_id": "145737475", "delivery_status": "SS" }

// 2nd — not delivered, desc_code is the reason
{ "rc": 100, "rm": "...", "ref_id": "145737477",
  "delivery_status": "SF", "desc_code": "UNDELIV" }
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. This tier reports in delivery_status: SS is DELIVRD (delivered); SF is not delivered, with desc_code carrying one of the reasons below.

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

    Sign up online — it comes with free SMS trial credit and needs no credit card. For larger volumes, or a # Sender ID registration for Hong Kong, tell us the use case and expected volume and we configure routes suited to your destinations.

  2. 2

    Apply for an API key

    Apply for the key in the dashboard; once it is activated, 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 to test the API first?

Once you have signed up you can apply for an API key in the dashboard and, when it is activated, send to your own phone on the trial credit — no contract required first. Taiwan (+886) takes no sender, so you can test as soon as the key is active. Hong Kong needs a Sender ID registered on your account; if you do not have one yet, contact us to arrange a test.

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 reporting delivery_status = SS (that is, 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 plus the status (status on the 1st callback, delivery_status on the 2nd) so a redelivered callback does not double-decrement stock or double-send an email.

How should I handle the non-DELIVRD statuses? +

These arrive in the 2nd callback's desc_code, which is present only when delivery_status is SF. 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 delivery_status = SS (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, sign up, apply for an API key in the dashboard and, once it is activated, send to your own phone on the trial credit: Taiwan (+886) takes no sender, so it can be tested straight away; Hong Kong needs a Sender ID registered on your account, and if you do not have one yet you can contact us to arrange a test.

Free API access. No credit card. Test before you commit.

Sign up for free SMS trial credit and API access — no credit card required. Self-serve account, so you can confirm the platform fits your use case first; enterprise teams can also contact us for assisted onboarding.