Get your credentials. Once your subscription
is active we create your app, and you'll find your base URL,
X-Api-Key and X-Api-Secret in the dashboard.
Keep them on your server. Store them in
environment variables: ZADX_BASE_URL, ZADX_API_KEY and
ZADX_API_SECRET — never hard-code them.
Send a request. Call
POST /sms/send or POST /otp/send with a JSON body.
curl
curl -X POST "$ZADX_BASE_URL/sms/send" \
-H "X-Api-Key: $ZADX_API_KEY" \
-H "X-Api-Secret: $ZADX_API_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1001-shipped" \
-d '{
"to": "01012345678",
"message": "Hello from ZADX - your order has shipped."
}'
Authentication
Every API request needs
X-Api-Key and X-Api-Secret. The Idempotency-Key
header is required on send requests; retrying with the same key and content never
charges an SMS twice.
Header
Required
Notes
X-Api-Key
Always
Your app's public key.
X-Api-Secret
Always
Your app's private secret.
Idempotency-Key
On send requests
The same key with the same content returns
the original response.
Content-Type
On POST
application/json
Protect your secret
Call ZADX from your server only. Never put
X-Api-Secret inside a mobile app, browser code, or a public
repository — if it leaks, rotate it immediately. For extra protection,
restrict your app to your server IPs.
POST
/otp/send
Send a verification code. You generate
the OTP; ZADX delivers it once and never retries or resends automatically.
Default templateYour {app_name} verification code is: {otp}
Field
Type
Required
Notes
to
string
Yes
Egyptian mobile number, e.g. 01012345678 or
+201012345678.
otp
string
Yes
4 to 6 digits.
template_id
integer
No
ID of an active, approved template that
belongs to your app. When omitted, the default template is used.
sender_id
string
No
Must be assigned to your app, e.g. ZADX.
locale
string
No
ar or en, default
ar. Approved templates use their own saved language and
text.
import os
import requests
res = requests.post(
f"{os.environ['ZADX_BASE_URL']}/otp/send",
headers={
"X-Api-Key": os.environ["ZADX_API_KEY"],
"X-Api-Secret": os.environ["ZADX_API_SECRET"],
"Idempotency-Key": "signup-42-1",
},
json={"to": "01012345678", "otp": "482917"},
timeout=15,
)
data = res.json()
The server fills in
{otp} and {app_name} for you and ignores any message text sent
directly — free text can't be sent through this endpoint. You'll find your approved
template IDs in the dashboard; an unknown or inactive ID returns
422 invalid_template_id without sending or charging.
Success response
A paid send reserves credit
and returns 202 queued. The worker makes one attempt with the provider; on a
timeout the status becomes pending_verification and the credit stays
reserved — no automatic resend or refund.
A regular transactional SMS. You
provide the full message text — no template substitution. Provider failures and
timeouts are not retried automatically. Your app name is appended as a mandatory last
line on every message.
Content policy enforcement
The API checks SMS messages before sending or
reserving credit. A violation is blocked, one strike is added and 10% of the
current plan quota is deducted; a third strike suspends the account. OTP
messages are exempt.
Field
Type
Required
Notes
to
string
Yes
Same phone rules as /otp/send.
message
string
Yes
Up to 800 characters, billed per segment.
sender_id
string
No
Must be assigned to your app, e.g. ZADX.
curl
curl -X POST "$ZADX_BASE_URL/sms/send" \
-H "X-Api-Key: $ZADX_API_KEY" \
-H "X-Api-Secret: $ZADX_API_SECRET" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: order-1001-shipped" \
-d '{
"to": "01012345678",
"message": "Hello from ZADX - your order has shipped."
}'
const res = await fetch(`${process.env.ZADX_BASE_URL}/sms/send`, {
method: "POST",
headers: {
"X-Api-Key": process.env.ZADX_API_KEY,
"X-Api-Secret": process.env.ZADX_API_SECRET,
"Content-Type": "application/json",
"Idempotency-Key": "order-1001-shipped",
},
body: JSON.stringify({
to: "01012345678",
message: "Hello from ZADX - your order has shipped.",
}),
});
const data = await res.json();
Python
import os
import requests
res = requests.post(
f"{os.environ['ZADX_BASE_URL']}/sms/send",
headers={
"X-Api-Key": os.environ["ZADX_API_KEY"],
"X-Api-Secret": os.environ["ZADX_API_SECRET"],
"Idempotency-Key": "order-1001-shipped",
},
json={
"to": "01012345678",
"message": "Hello from ZADX - your order has shipped.",
},
timeout=15,
)
data = res.json()
Encoding & billing
Plain English (GSM-7): 160 characters
per segment.
Arabic, emoji, or special characters (UCS-2):
70 characters per segment.
cost_credits in the response equals
segments.
Maximum 6 segments per call; longer messages return
422 too_many_segments.
GET
/sms/balance
Returns the remaining credits and
the active plan for the authenticated app.
Fetch a single message. The
status starts as queued, then becomes sent or
delivered, failed after an explicit rejection, or
pending_verification when provider acceptance is unknown.
An explicit provider rejection becomes failed and
the credit is refunded. A timeout becomes pending_verification with the
credit kept reserved — don't auto-resend.
Rate limits
Exceeding a limit returns
429. Wait before retrying.
Scope
Default
Behavior
Per app
60 sends / minute
Some plans can go higher via
max_sms_per_minute.
Per recipient phone
1 send / minute
Prevents accidental double sends. Accepted
sends count whether sent, delivered, or still queued.
Per recipient phone
5 sends / hour
Stops loops from flooding a single number.
Idempotency
The Idempotency-Key header is
required on write requests.
Use a stable value for each
logical action, such as order-{orderId}-shipped or
signup-{userId}-{attempt}.
The same key with the same content returns the original
response.
The same key with different content returns
409 idempotency_conflict.