ZI² SMS

Send a text from your app in one call

One endpoint, one header, one JSON body. No SDK to install, no device to manage, no callback to host. The number your customer sees is yours.

Full API reference

1

Get a key

In Settings, open API Keys and create one with the SMS scope. Copy it when it is shown. It is stored as a hash and never displayed again.

2

Send

One POST with a recipient and a message. The response tells you whether the carrier accepted it and gives you an id you can quote later.

3

There is no step three

The message goes out through whichever provider the organisation has set as default. Change carrier later and nothing in your code changes.

Send one message

curl
curl -X POST /api/v1/sms/send \
  -H "Authorization: Bearer zi2_pk_your_key_here" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-18422-confirm" \
  -d '{"to":"+15551234567","body":"Your code is 847291"}'
Node
const res = await fetch('/api/v1/sms/send', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${process.env.ZI2_SMS_API_KEY}`,
    'Content-Type': 'application/json',
    'Idempotency-Key': `order-${orderId}-confirm`,
  },
  body: JSON.stringify({ to: '+15551234567', body: 'Your code is 847291' }),
});
const out = await res.json();
if (!out.success) throw new Error(out.error.code);
Python
r = requests.post(
    "/api/v1/sms/send",
    headers={
        "Authorization": f"Bearer {os.environ['ZI2_SMS_API_KEY']}",
        "Idempotency-Key": f"order-{order_id}-confirm",
    },
    json={"to": "+15551234567", "body": "Your code is 847291"},
    timeout=15,
)
r.raise_for_status()

Keep the messageId from the response. It is the id of the send in the audit record, so it is what you quote when asking what happened to a given message.

Send many at once

Up to 500 messages in one call, one credit per message actually sent. The call returns 200 and you read the per-recipient results, so one bad number does not fail the batch.

curl
curl -X POST /api/v1/sms/send/bulk \
  -H "Authorization: Bearer zi2_pk_your_key_here" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: campaign-4417" \
  -d '{"messages":[
        {"to":"+15551234567","body":"Your code is 847291"},
        {"to":"+15557654321","body":"Your code is 112358"}
      ]}'

Retries are safe

Send an Idempotency-Key and a repeat of the same request returns the original result and charges nothing the second time. Reusing the same key with a different recipient or message is refused, so a copy-paste mistake cannot quietly send something unintended. Pick the key from your own data, such as an order reference, rather than a random value, so a retry after a timeout reuses it.

Limits

Requests per minute, per key
600
Messages per bulk call
500
Messages per rolling 24 hours, per key
500
Message length
1 to 1600 characters, sent as multipart when needed

A failed send is not charged and does not count against the daily limit.

Responses

Every error body has the same shape, so one branch handles all of them.

StatusCodeMeaning
200Accepted by the carrier
400INVALID_REQUESTThe number is not E.164, or the message is empty or too long
401API_KEY_INVALIDThe key is unknown or has been revoked
403SCOPE_MISSINGThe key does not carry the scope this route needs
403DESTINATION_NOT_ALLOWEDPremium, satellite, or a country outside the allowlist
409NO_SMS_PROVIDERThe organisation has no active provider yet
429DAILY_CAP_REACHEDThe key hit its rolling 24 hour limit
424SMS_SEND_FAILEDThe carrier rejected it

A 200 means the carrier accepted the message. It does not promise a handset displayed it.

What is blocked, and why

Premium-rate, shared-cost and satellite ranges are refused. These are the destinations used in pumping fraud, where a stolen key is pointed at a range that pays the attacker for every message it receives and the bill lands on the organisation that owns the key. Blocking them removes the payout. Every send is recorded against the key with recipient, length, provider, outcome and calling IP, so if a key is misused the trail shows exactly what went out.

Ready to wire it up

Create a key with the SMS scope and the first message can go out in a few minutes. Keep the key server side, one per application, so you can revoke one without breaking the rest.