Loading

The public reference for the ZADX SMS API. Send OTP codes and SMS from any backend with two headers and one JSON request.

Get your API keys

Quick start

Send your first SMS in under a minute.

  1. 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.
  2. Keep them on your server. Store them in environment variables: ZADX_BASE_URL, ZADX_API_KEY and ZADX_API_SECRET — never hard-code them.
  3. 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 template Your {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.
curl
curl -X POST "$ZADX_BASE_URL/otp/send" \
  -H "X-Api-Key: $ZADX_API_KEY" \
  -H "X-Api-Secret: $ZADX_API_SECRET" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: signup-42-1" \
  -d '{
    "to": "01012345678",
    "otp": "482917"
  }'

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.

JSON
{
  "id": 4127,
  "status": "queued",
  "to": "+201012345678",
  "sender_id": "ZADX",
  "encoding": "gsm7",
  "char_count": 32,
  "segments": 1,
  "cost_credits": 1,
  "remaining_credits": 996,
  "created_at": "2026-05-28T15:32:11+00:00"
}
POST

/sms/send

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."
  }'

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.

curl
curl "$ZADX_BASE_URL/sms/balance" \
  -H "X-Api-Key: $ZADX_API_KEY" \
  -H "X-Api-Secret: $ZADX_API_SECRET"
GET

/sms/sender-ids

Lists only the sender IDs explicitly assigned to the authenticated app.

curl
curl "$ZADX_BASE_URL/sms/sender-ids" \
  -H "X-Api-Key: $ZADX_API_KEY" \
  -H "X-Api-Secret: $ZADX_API_SECRET"
GET

/messages

A paginated log of every send for the authenticated app. Use ?per_page=25&page=1 to paginate.

curl
curl "$ZADX_BASE_URL/messages?per_page=25&page=1" \
  -H "X-Api-Key: $ZADX_API_KEY" \
  -H "X-Api-Secret: $ZADX_API_SECRET"
GET

/messages/{id}

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.

curl
curl "$ZADX_BASE_URL/messages/4127" \
  -H "X-Api-Key: $ZADX_API_KEY" \
  -H "X-Api-Secret: $ZADX_API_SECRET"

Error codes

Every API error uses the same shape.

JSON
{
  "error": {
    "code": "snake_case_code",
    "message": "Human-friendly explanation"
  }
}
HTTP Error code How to fix
401 missing_credentials Add X-Api-Key and X-Api-Secret.
401 invalid_credentials Update your app credentials.
402 no_active_subscription Buy or renew a plan.
402 quota_exhausted Add another plan or upgrade.
403 app_inactive The app was suspended or cancelled by an admin.
403 ip_not_allowed Add the caller IP to your allowlist.
403 sender_id_not_allowed Use a sender ID assigned to your app.
403 mode_not_allowed Use the endpoint allowed for your app's mode.
409 idempotency_conflict Reuse a key only with the same content.
422 missing_idempotency_key Add a unique Idempotency-Key header to every send request.
422 validation_failed Check the request body — e.g. a badly formatted template_id.
422 invalid_template_id The template is unknown, inactive, or belongs to another app. Nothing is sent or charged.
422 invalid_phone Pass a valid Egyptian mobile number.
422 message_too_long Keep the message under 800 characters.
422 too_many_segments Keep the text within 6 SMS segments.
429 rate_limited_app Slow down and respect the Retry-After header.
429 rate_limited_phone_minute This recipient already hit the per-minute limit.
429 rate_limited_phone_hour This recipient already hit the hourly limit.
502 provider_failed
provider_unavailable
driver_exception
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.
  • Keys are kept for 24 hours.