KILO SMS

Kilo SMS API

من نفس الـ API تقدر تبعت لرقم واحد أو لحد 500,000 رقم مصري. هات رقم السيندر من حسابك، اعمل معاينة، وبعد مراجعة عدد الرسائل والتكلفة أكّد الإرسال باستخدام رقم الدفعة. تابع الحالة بنفس الرقم. لو طلب التأكيد انقطع، ابعت نفس رقم الدفعة تاني.

Authentication

Use HTTPS and send your 64-character hexadecimal API key in the Authorization header on every request. Create or replace the key at /KiloSmsApi/Key. Copy it when issued. Replacing it revokes the previous key.

A frozen account can call Senders and Status with its existing key. Preview, Send, and Discard return 403 account_restricted. A blocked account receives 401 unauthorized on all endpoints. Password and email changes revoke browser sessions. Manage API keys separately on the key page.

Authorization: Bearer YOUR_API_KEY
Accept: application/json

The base URL in the examples is your Kilo SMS installation. If the app is under a virtual directory, include that directory in the base URL. The API returns UTF-8 JSON.

Endpoints

Method and path Request Successful response
GET /KiloSmsApi/Senders Bearer header 200 with the sender IDs enabled for your account
POST /KiloSmsApi/Preview Form fields SenderId, Body and Recipients, or a multipart upload 201 with a draft ID, validation counts and estimated cost
POST /KiloSmsApi/Send URL-encoded BatchId 202 while queued or processing. 200 if already complete
GET /KiloSmsApi/Status batchId query parameter 200 with the draft or sending state
POST /KiloSmsApi/Discard URL-encoded BatchId 200 with success true for an unconfirmed draft

Complete sending example

This Bash example uses cURL 7.76 or newer and jq. Set KILOSMS_BASE_URL and KILOSMS_API_KEY in your environment. Set KILOSMS_SENDER_ID to an approved sender ID and KILOSMS_RECIPIENT to an Egyptian mobile number you control. The same workflow applies when you preview a file of recipients.

#!/usr/bin/env bash
set -euo pipefail

: "${KILOSMS_BASE_URL:?Set your HTTPS Kilo SMS installation URL}"
: "${KILOSMS_API_KEY:?Set your API key}"
: "${KILOSMS_SENDER_ID:?Set your enabled sender ID}"
: "${KILOSMS_RECIPIENT:?Set an Egyptian test number you control}"

api="${KILOSMS_BASE_URL%/}/KiloSmsApi"

api_json() {
    local response
    if response="$(curl --silent --show-error --fail-with-body \
        --header "Authorization: Bearer $KILOSMS_API_KEY" \
        --header "Accept: application/json" "$@")"; then
        printf '%s\n' "$response"
    else
        printf '%s\n' "$response" >&2
        return 1
    fi
}

api_json "$api/Senders" | jq .

preview="$(api_json "$api/Preview" \
    --data-urlencode "SenderId=$KILOSMS_SENDER_ID" \
    --data-urlencode "Body=Kilo SMS test" \
    --data-urlencode "Recipients=$KILOSMS_RECIPIENT")"
printf '%s\n' "$preview" | jq .
batch_id="$(jq -er '.batchId' <<< "$preview")"
printf 'Save this preview ID for any confirmation retry: %s\n' "$batch_id"

read -r -p 'Queue this preview? [y/N] ' answer
if [[ "$answer" != "y" && "$answer" != "Y" ]]; then
    api_json "$api/Discard" --data-urlencode "BatchId=$batch_id" | jq .
    exit 0
fi

sending="$(api_json "$api/Send" --data-urlencode "BatchId=$batch_id")"
printf '%s\n' "$sending" | jq .
batch_id="$(jq -er '.batchId' <<< "$sending")"

for ((attempt=1; attempt<=30; attempt++)); do
    status="$(api_json --get "$api/Status" --data-urlencode "batchId=$batch_id")"
    printf '%s\n' "$status" | jq .
    if [[ "$(jq -er '.state' <<< "$status")" == "complete" ]]; then
        exit 0
    fi
    sleep 3
done

printf 'Still queued or processing. Continue polling batch %s.\n' "$batch_id"

Preview a recipient file

Upload one candidate per line in a UTF-8 text file. Comma-separated entries are also accepted. A file can contain up to 500,000 candidate entries before filtering. Use the returned batchId in the same Send, Status and Discard requests.

curl --silent --show-error --fail-with-body \
  --header "Authorization: Bearer $KILOSMS_API_KEY" \
  --header "Accept: application/json" \
  --form-string "SenderId=$KILOSMS_SENDER_ID" \
  --form "BodyFile=@message.txt;type=text/plain" \
  --form "RecipientsFile=@recipients.txt;type=text/plain" \
  "${KILOSMS_BASE_URL%/}/KiloSmsApi/Preview"
Preview field Value
SenderId Positive integer from Senders
Body or BodyFile UTF-8 SMS text. One field per request. Limit 1,000 UTF-16 units after converting line breaks to CRLF. BodyFile is a multipart upload
Recipients or RecipientsFile Egyptian mobile numbers in text, from one to 500,000 candidate entries. One field per request. RecipientsFile is a multipart UTF-8 upload
Content-Type Use application/x-www-form-urlencoded for text fields, or multipart/form-data for files. Let your HTTP client generate the multipart boundary

Use Recipients with a newline or comma between numbers in a small request. For large inputs use RecipientsFile. The recipient text limit is 12,000,000 UTF-16 units and the recipient file limit is 36,000,000 UTF-8 bytes. IIS or a proxy may reject a request before it reaches the API.

Response examples

Senders · 200

{
  "senders": [
    {"senderId": 17, "senderName": "KILO SMS"}
  ]
}

Preview · 201

This example represents a preview with two eligible recipients, one duplicate, one invalid entry and one recipient blocked by sender permissions. A successful preview reserves zero credits and expires after 30 minutes.

{
  "batchId": "11223344-5566-4788-99aa-bbccddeeff00",
  "entered": 5,
  "invalid": 1,
  "duplicates": 1,
  "legacyConverted": 0,
  "blocked": 1,
  "eligible": 2,
  "unitsPerRecipient": 1,
  "totalUnits": 2,
  "totalPrice": 0.50,
  "walletUnits": 10000,
  "networks": [
    {"name": "Vodafone", "prefix": "2010", "eligible": 1, "blocked": 0},
    {"name": "e& / Etisalat", "prefix": "2011", "eligible": 1, "blocked": 0},
    {"name": "WE", "prefix": "2015", "eligible": 0, "blocked": 1}
  ],
  "expiresUtc": "2026-11-08T13:00:00.0000000Z",
  "textAnalysis": {
    "language": "english",
    "encoding": "gsm7",
    "characters": 13,
    "units": 1,
    "encodingUnits": 13,
    "lineBreaks": 0,
    "issueCount": 0,
    "issues": [],
    "error": null
  },
  "error": null
}

Preview with blocked text · 422

The example body contains an invisible U+200B between A and B.

{
  "error": "invalid_text",
  "textAnalysis": {
    "language": "english",
    "encoding": "empty",
    "characters": 3,
    "units": 0,
    "encodingUnits": 0,
    "lineBreaks": 0,
    "issueCount": 1,
    "issues": [
      {
        "character": "",
        "codePoint": "U+200B",
        "kind": "blocked",
        "count": 1,
        "replacement": null,
        "position": 1
      }
    ],
    "error": "invalid_text"
  }
}

Preview with zero eligible recipients · 422

{
  "batchId": null,
  "entered": 1,
  "invalid": 0,
  "duplicates": 0,
  "legacyConverted": 0,
  "blocked": 1,
  "eligible": 0,
  "unitsPerRecipient": 1,
  "totalUnits": 0,
  "totalPrice": 0,
  "walletUnits": 10000,
  "networks": [
    {"name": "WE", "prefix": "2015", "eligible": 0, "blocked": 1}
  ],
  "expiresUtc": null,
  "textAnalysis": {
    "language": "english",
    "encoding": "gsm7",
    "characters": 13,
    "units": 1,
    "encodingUnits": 13,
    "lineBreaks": 0,
    "issueCount": 0,
    "issues": [],
    "error": null
  },
  "error": "no_recipients"
}

Send · 202

{
  "batchId": "11223344-5566-4788-99aa-bbccddeeff00",
  "state": "queued",
  "eligible": 2,
  "accepted": 0,
  "rejected": 0,
  "unknown": 0,
  "dispatchDelayed": false,
  "walletUnits": 9998,
  "pending": 2
}

Status or completed Send retry · 200

{
  "batchId": "11223344-5566-4788-99aa-bbccddeeff00",
  "state": "complete",
  "eligible": 2,
  "accepted": 1,
  "rejected": 0,
  "unknown": 1,
  "dispatchDelayed": false,
  "walletUnits": 9998,
  "pending": 0
}
State or field Meaning
draft Preview exists and awaits confirmation
queued Confirmed and waiting for the worker
processing Worker has started processing the batch
complete Every eligible recipient has a recorded gateway outcome
accepted Gateway accepted the submission. Handset delivery is a separate event
rejected A definitive rejection or confirmed inability to send. The associated credit is released back to the original top-up lots
unknown Submission outcome could not be confirmed. The worker records it for review and skips automatic re-send
pending Eligible recipients whose outcomes are still pending, including in-flight work
dispatchDelayed True when the batch remains queued for more than one minute

Send reserves credit at confirmation. Repeating Send with the same BatchId returns its current state and uses its existing reservation. If an equivalent recent batch already exists, Send can return that batch’s canonical ID. Save the batchId in the response and use it for subsequent Status requests. On a timeout or 503 after calling Send, retry using the same preview ID.

Discard · 200

{"success": true}

Discard expires an unconfirmed preview. Repeating it for an already expired or absent draft also succeeds. A confirmed batch returns already_submitted.

Error responses

Application errors return an error code as JSON. For example, Send with an insufficient wallet returns HTTP 402.

{"error": "insufficient_balance"}
HTTP error What to do
400 invalid_request Check the fields, sender ID, batch UUID and duplicate parameters
401 unauthorized Supply a valid Bearer key. The response includes WWW-Authenticate: Bearer
402 insufficient_balance Add credit and retry the same preview while it remains valid
403 https_required Use the HTTPS installation URL
403 sender_not_approved Choose an enabled sender ID from Senders
403 account_restricted Check the account state in the customer workspace. If a key changed while a write was in progress, use the current key
404 not_found Check the batch ID and the account that owns it. An expired draft may already have been cleaned up
409 preview_changed Create a fresh preview after text, routing or pricing rules change
409 already_submitted Use Status for the confirmed batch
410 expired Create a new preview
413 too_many_numbers Split the list or reduce the upload size
415 invalid_content_type Choose the supported URL-encoded form or multipart request format
422 empty_text Provide nonempty SMS text
422 text_too_long Reduce the normalized text to 1,000 UTF-16 units
422 invalid_text Review the blocked code points in textAnalysis
422 invalid_encoding Save the uploaded text as UTF-8
422 no_recipients Review invalid, duplicate and blocked counts, then correct the recipient input or sender permissions
429 too_many_drafts Discard unused previews or wait for expiration and cleanup
503 pricing_unavailable Ask the administrator to configure the price
503 unavailable Retry a transient failure. For a Send retry keep the same BatchId