Overview

With the TurkeySMS API you can send SMS and OTP messages from your application, schedule sends, manage your contacts, query your balance and Sender IDs, and retrieve send reports. All requests are sent over HTTPS with the POST method, and responses are in JSON.

The information on this page is based on how the API behaves in the live system. The sections follow the layout of API Center in the panel.

SectionEndpoints
Key check/auth/post/check/
Messaging/sms/send, /group/send, /group/sendMixed, /otp/send, /otp/detailed
Contacts/groups/create, /groups/edit, /groups/delete, /groups/list, /contacts/add, /blacklist/post/add, /blacklist/post/status
Queries and reports/balance/, /senderid/check, /sms/status, /reports/basic, /reports/detailed
WebhookEvent notifications are sent to your server

Getting started

Requirements

  • An active TurkeySMS account. If the account is not active, requests are rejected (TS-1030 on most endpoints).
  • Enough balance. For send requests, your balance must cover the total number of SMS to be sent.
  • An approved Sender ID. For SMS and group sends, put a Sender ID that is approved on your account in the title field. You can list your approved Sender IDs with Sender ID query.
  • An API key with the required permissions. Each endpoint requires specific permissions (see Permissions).

Quick start

  1. In the panel, create a key under API Center → My Keys → New Key. For your first SMS, turn on the “Allow POST requests” and “Send SMS” permissions.
  2. The key is shown only once. Copy it and store it in a safe place on your server (for example, an environment variable).
  3. Verify your key with the Key check endpoint.
  4. Send your first SMS with the example below. For Turkish text, send sms_lang as 1.
curl -X POST https://api.turkeysms.com.tr/sms/send \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "title": "BASLIGINIZ",
  "sentto": "905XXXXXXXXX",
  "text": "Siparişiniz kargoya verildi.",
  "sms_lang": 1
}'
$payload = [
    'api_key'  => 'API_ANAHTARINIZ',
    'title'    => 'BASLIGINIZ',
    'sentto'   => '905XXXXXXXXX',
    'text'     => 'Siparişiniz kargoya verildi.',
    'sms_lang' => 1,
];

$ch = curl_init('https://api.turkeysms.com.tr/sms/send');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key":  "API_ANAHTARINIZ",
    "title":    "BASLIGINIZ",
    "sentto":   "905XXXXXXXXX",
    "text":     "Siparişiniz kargoya verildi.",
    "sms_lang": 1,
}

r = requests.post("https://api.turkeysms.com.tr/sms/send", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key:  'API_ANAHTARINIZ',
  title:    'BASLIGINIZ',
  sentto:   '905XXXXXXXXX',
  text:     'Siparişiniz kargoya verildi.',
  sms_lang: 1,
};

const r = await fetch('https://api.turkeysms.com.tr/sms/send', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();

In a successful request, result is true and result_code is TS-1024. For details, see the Sending SMS section.

General rules

Base URL and request format

  • Base URL: https://api.turkeysms.com.tr. Endpoint paths are appended to this URL (for example, https://api.turkeysms.com.tr/sms/send). The URL has no version prefix.
  • All endpoints accept POST only.
  • Send the body as JSON (Content-Type: application/json, UTF-8). Form data is also accepted; the examples use JSON. The query string is not read.
  • Use the paths exactly as written on this page. /balance/ and /auth/post/check/ are written with a trailing slash; /senderid/check and the others are written without one. A wrong path returns a redirect (301) or 404.

Authentication

Send your API key in the api_key field of the body with every request. A key sent in an HTTP header (for example, Authorization) is not read.

Use the API key on the server side only. Do not put it in code that runs in the browser, in a mobile app package or in a public repository. If you think the key has been exposed, rotate or revoke it in the panel (see API keys).

Response format

Most endpoints use the envelope below. In successful responses, endpoint-specific fields are added to the same object.

JSON — error example
{
  "result": false,
  "result_code": "TS-1031",
  "result_message": "Invalid API key. Authentication failed."
}

The Reports and Number blocking endpoints use the status field ("success" or "error") instead of result.

Evaluate the result from the body, not from the HTTP code. Some endpoints return a failed result with HTTP 200 (for example, when a group name already exists or a report is not found). Check the result (or status) and result_code fields. result_message texts are in English and may change; use result_code in your program.

On an unexpected server error, the response is usually HTTP 500 with result_code SRV-ERR (TS-5000 in Key check). Rarely, the response may not be JSON, or may contain an error field instead of result_code; also catch JSON parsing errors in your code. You can retry the request after a short while; on send endpoints, before retrying, check whether the message went out with SMS status query or Reports.

Rate limit

A per-minute send limit may apply to /sms/send at the account level. This limit is set on your account by TurkeySMS. The limit counts the messages (recipients) recorded from your account in the last minute, not the number of requests. When the limit is reached, the response is HTTP 429 with TS-1060; wait a while and retry.

This limit is separate from the hourly, daily and monthly limits you can set for a key in the API Center (see Limits and IP allowlist).

Date and time

Dates use the YYYY-MM-DD format, and times use HH:MM:SS (hour:minute:second) or, in some fields, HH:MM. Date and time values returned in responses carry no time zone information.

API keys

API keys are managed in the panel under API Center → My Keys. Each key has its own permissions; we recommend creating separate keys for your different systems and giving each one only the permissions it needs.

Creating a key

  1. Open the wizard with the New Key button: “Details”, “Scopes”, “Limits”.
  2. When the key is created, it is shown only once. It cannot be displayed again after you leave the page; if you lose it, rotate the key.

Key states

In the panel, keys appear as “Active”, “Paused”, “Expired” or “Revoked”. Only keys in the Active state can make requests; requests made with a key shown in any other state in the panel are rejected with TS-1031 or TS-1035. If an expiry date is set for the key, requests made after the date passes are rejected with TS-1035. You can reactivate a paused key; a revoked key cannot be restored.

Rotating a key

In the key details, Danger Zone → Rotate key creates a new key; the permissions and settings are moved to the new key. If you tick the “Keep the old key working for 24 more hours” option, the old key stays valid for 24 more hours; during this time you can move your systems to the new key. If you do not tick the option, the old key becomes invalid immediately. After the period ends, requests made with the old key are rejected with TS-1035 or TS-1031. If the key has leaked, do not tick the option.

Permissions

Permissions are turned on and off in the Scopes tab of the key details. The table below shows the endpoints where each permission is checked and the code returned when the permission is off. “Allow POST requests” is not a sending method but a separate permission; it is checked only on the endpoints listed.

Permission (panel)EndpointsIf off
Allow POST requests/sms/send, /sms/status, /otp/detailed, /reports/basic, /reports/detailedTS-1061
Send SMS/sms/sendTS-1062
Send to group/group/send, /group/sendMixedTS-1067
Send OTP/otp/sendTS-1036
Advanced OTP/otp/detailedTS-1037
Create group/groups/createTS-1081
Edit group/groups/editTS-1084
Delete group/groups/deleteTS-1085
List groups/groups/listTS-1089
Add number/contacts/addTS-1065
Block number/blacklist/post/add, /blacklist/post/statusTS-1065
Check balance/balance/TS-1065
Check SMS status/sms/status, /reports/basic, /reports/detailedTS-1063
Check Sender ID/senderid/checkTS-1038

/auth/post/check/ requires no permission. You can use this endpoint to query a key's permissions.

Limits and IP allowlist

In the “Limits” step of the wizard and in the “Limits” and “Security” tabs of the key details, you can set hourly, daily and monthly limits, an IP allowlist and an expiry date for a key. These rules apply only when they are set in the API Center. A limit left empty or set to 0 means no limit; an empty allowlist means no IP restriction.

RuleWhen the request is rejectedCode (HTTP)
Hourly limitWhen the number of requests made with the key within the current hour (for example 14:00–14:59) reaches the limit.TS-1068 (429)
Daily limitWhen the number of requests made with the key on the current day reaches the limit.TS-1069 (429)
Monthly limitWhen the number of requests made with the key in the current calendar month reaches the limit.TS-1073 (429)
IP allowlistWhen the IP address the request comes from is not on the list. If the key has its own list, only that list is used; otherwise the account's list is used. The list can contain single IP addresses or CIDR ranges (for example 203.0.113.0/24).TS-1066 (403)
Expiry dateAfter the date passes. The key is valid until the end of the set day.TS-1035 (403)
  • The limits count all API requests made with the key, including sending, query, report and contact requests.
  • Hours, days and months are calculated in Türkiye time (Europe/Istanbul).
  • Requests rejected because of these rules do not count toward the limits.
  • When you receive HTTP 429, wait for the next hour, day or month to start, or raise the limit in the panel.
  • These limits are separate from the per-minute send limit (TS-1060) that TurkeySMS sets on your account.

Key check

Returns whether your API key is valid, its permissions and an account summary. You can use it to check the permissions before taking an integration live.

POST https://api.turkeysms.com.tr/auth/post/check/

Required permission: None. An active API key is enough.

The URL is written with a trailing slash: /auth/post/check/. The old /auth/check URL is not used (404).

Parameters

ParameterTypeStatusDescription
api_keystringRequiredYour API key. 20–128 characters.

Request example

curl -X POST https://api.turkeysms.com.tr/auth/post/check/ \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ"
}'
$payload = [
    'api_key' => 'API_ANAHTARINIZ',
];

$ch = curl_init('https://api.turkeysms.com.tr/auth/post/check/');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key": "API_ANAHTARINIZ",
}

r = requests.post("https://api.turkeysms.com.tr/auth/post/check/", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key: 'API_ANAHTARINIZ',
};

const r = await fetch('https://api.turkeysms.com.tr/auth/post/check/', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();

Successful response

JSON — 200 OK
{
  "result": true,
  "result_code": "TS-1000",
  "result_message": "Authentication audit successful",
  "key_details": {
    "status": "Active",
    "permissions": {
      "post_request": true,
      "send_single_sms": true,
      "send_otp": true,
      "check_balance": true,
      "check_senderid": true,
      "manage_groups": false,
      "send_group_sms": true,
      "send_otp_advanced": false,
      "create_group": false,
      "edit_group": false,
      "delete_group": false,
      "list_groups": false,
      "add_contact": false,
      "delete_contact": false,
      "block_number": false,
      "check_sms_status": true
    }
  },
  "account_summary": {
    "account_status": "Active",
    "balance": { "main": 1500, "international": 0 },
    "global_sending": false
  },
  "audit_info": {
    "request_ip": "203.0.113.10",
    "checked_at": "2026-10-01 14:30:00"
  }
}
FieldDescription
key_details.statusThe key's state. Because only active keys get a response, it is Active in a successful response.
key_details.permissions16 permission fields (true/false). Their panel equivalents are in the table below.
account_summary.account_statusThe account's state. It is Active in a successful response.
account_summary.balance.mainThe SMS credit in your account (integer).
account_summary.balance.international, account_summary.global_sendingAdditional fields.
audit_info.request_ipThe IP address the request came from.
audit_info.checked_atThe date and time of the check.

Panel equivalents of the permission fields:

FieldPanel permission
post_requestAllow POST requests
send_single_smsSend SMS
send_group_smsSend to group
send_otpSend OTP
send_otp_advancedAdvanced OTP
create_groupCreate group
manage_groupsCreate group (old name; carries the same value as create_group and is kept for backward compatibility)
edit_groupEdit group
delete_groupDelete group
list_groupsList groups
add_contactAdd number
delete_contactHas no equivalent in the panel; not used by the endpoints documented on this page.
block_numberBlock number
check_balanceCheck balance
check_sms_statusCheck SMS status
check_senderidCheck Sender ID

Error response

JSON — 400
{
  "result": false,
  "result_code": "TS-1031",
  "result_message": "Invalid API key"
}
CodeHTTPMeaning
TS-1031401api_key is missing, is not a string or is outside 20–128 characters.
TS-1031400The key was not found or is not in the “Active” state.
TS-1030400The account is not active.
TS-5000500Unexpected server error.

Sending SMS

Sends the same text to one or more numbers. When the request succeeds, the messages are accepted for delivery to the operator.

POST https://api.turkeysms.com.tr/sms/send

Required permissions: “Allow POST requests” and “Send SMS” (API Center → My Keys → key → Scopes)

Parameters

ParameterTypeStatusDescription
api_keystringRequiredYour API key.
senttostringRequiredRecipient number. Separate multiple numbers with commas, semicolons or line breaks. At most 500 distinct numbers per request (50,000 for scheduled sends). If a number appears more than once, it receives the message once.
titlestringRequiredA Sender ID approved on your account. At most 11 characters; Turkish characters (ç, ğ, ı, ö, ş, ü) count as two characters.
textstringRequiredMessage text. At most 2,000 characters. The TS-L token in the text is converted to a line break.
sms_langintOptionalCharacter set and SMS count calculation: 0 English, 1 Turkish, 2 Arabic/Unicode. Default 2. For Turkish text, send 1 (see Message language and SMS count).
content_typeintOptionalContent label: 0 Transactional, 1 High Quality, 2 Advertising. Default 0. Returned only as a label in the response; it does not affect the send.
scheduled_datestringOptionalIf set, the send is scheduled. See Scheduled sending.
scheduled_timestringConditionalRequired when scheduled_date is sent.

Number format

Send numbers in international format, without a leading +: 905XXXXXXXXX. The API removes spaces, +, - and parentheses; removes the 00 from numbers that start with 00; and converts 05… and 10-digit 5… numbers to the 905… format. Numbers are not validated one by one; a wrong number is also accepted and added to the SMS count. Validate numbers on your side before sending.

Rules

  • Your balance must cover the total SMS count calculated for all recipients; if it does not, no message is sent (TS-1027).
  • The per-minute send limit applies to this endpoint (see Rate limit).
  • Your number blocking list is not applied on this endpoint (see Number blocking).

Request example

curl -X POST https://api.turkeysms.com.tr/sms/send \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "title": "BASLIGINIZ",
  "sentto": "905XXXXXXXXX,905YYYYYYYYY",
  "text": "Randevunuz yarın 10:00'\''da.TS-LBilgi için bizi arayın.",
  "sms_lang": 1
}'
$payload = [
    'api_key'  => 'API_ANAHTARINIZ',
    'title'    => 'BASLIGINIZ',
    'sentto'   => '905XXXXXXXXX,905YYYYYYYYY',
    'text'     => 'Randevunuz yarın 10:00\'da.TS-LBilgi için bizi arayın.',
    'sms_lang' => 1,
];

$ch = curl_init('https://api.turkeysms.com.tr/sms/send');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key":  "API_ANAHTARINIZ",
    "title":    "BASLIGINIZ",
    "sentto":   "905XXXXXXXXX,905YYYYYYYYY",
    "text":     "Randevunuz yarın 10:00'da.TS-LBilgi için bizi arayın.",
    "sms_lang": 1,
}

r = requests.post("https://api.turkeysms.com.tr/sms/send", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key:  'API_ANAHTARINIZ',
  title:    'BASLIGINIZ',
  sentto:   '905XXXXXXXXX,905YYYYYYYYY',
  text:     'Randevunuz yarın 10:00\'da.TS-LBilgi için bizi arayın.',
  sms_lang: 1,
};

const r = await fetch('https://api.turkeysms.com.tr/sms/send', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();

Successful response

JSON — 200 OK
{
  "result": true,
  "result_code": "TS-1024",
  "result_message": "SMS dispatched successfully.",
  "sms_id": 48213377,
  "number_of_sms": 2,
  "total_recipients": 2,
  "success_count": 2,
  "sms_lang": "Turkish",
  "content_type": "Transactional",
  "country": "Turkey-TR"
}
FieldDescription
sms_idThe message ID of the last recipient. For single-recipient sends, use it with SMS status query.
number_of_smsThe total SMS count for all recipients.
total_recipientsThe number of recipients after duplicates are removed.
success_countThe number of recipients accepted for sending. It is not the number of delivered messages; for delivery status, use Webhook or SMS status query.
sms_lang, content_typeLabels for the values you sent.
countryThe country label of the first recipient (for example, Turkey-TR, or GlobalSMS-GL for international numbers).

Error response

JSON — 403
{
  "result": false,
  "result_code": "TS-1027",
  "result_message": "Insufficient SMS credits. Please top up your account."
}
CodeHTTPMeaning
TS-1033400The body is not valid JSON or form data.
TS-1050401api_key is missing or shorter than 30 characters.
TS-1025400sentto is missing, shorter than 7 characters or contains no number.
TS-1051400title is missing.
TS-1029400title is longer than 11 characters.
TS-1026400text is empty or longer than 2,000 characters.
TS-1060400The recipient limit was exceeded: 500 (50,000 for scheduled sends).
TS-1070 / TS-1071 / TS-1072400The scheduling fields are invalid (see Scheduled sending).
TS-1031401The key was not found or is not active.
TS-1061403The “Allow POST requests” permission is off.
TS-1062403The “Send SMS” permission is off.
TS-1030403The account is not active.
TS-1028400The Sender ID was not found on your account or is not approved.
TS-1027403Insufficient balance.
TS-1060429The per-minute send limit was exceeded.
SRV-ERR500Unexpected server error.

Notes

  • If you have set up a webhook, the sms.sent, sms.delivered and sms.failed events are sent for this send. For which webhook an event goes to, see Webhook → Routing.
  • Before resending a request because of a timeout, check whether the first request was processed; otherwise the message may be sent twice.

Scheduled sending

When scheduled_date and scheduled_time are added to a /sms/send request, the messages are not sent immediately; they are queued to be sent at the specified time.

POST https://api.turkeysms.com.tr/sms/send

Required permissions: “Allow POST requests” and “Send SMS” (API Center → My Keys → key → Scopes)

Additional parameters

ParameterTypeStatusDescription
scheduled_datestringRequiredSend date, YYYY-MM-DD (for example, 2026-10-15).
scheduled_timestringRequiredSend time, HH:MM or HH:MM:ss (for example, 09:30).

The other parameters are the same as in Sending SMS.

Rules

  • The specified time must be in the future. The date and time are evaluated in Türkiye time (Europe/Istanbul).
  • At most 50,000 distinct numbers can be sent in one request.
  • The balance check and the per-minute send limit are applied at the time of the request.

Request example

curl -X POST https://api.turkeysms.com.tr/sms/send \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "title": "BASLIGINIZ",
  "sentto": "905XXXXXXXXX",
  "text": "Kampanyamız yarın başlıyor.",
  "sms_lang": 1,
  "scheduled_date": "2026-10-15",
  "scheduled_time": "09:30"
}'
$payload = [
    'api_key'        => 'API_ANAHTARINIZ',
    'title'          => 'BASLIGINIZ',
    'sentto'         => '905XXXXXXXXX',
    'text'           => 'Kampanyamız yarın başlıyor.',
    'sms_lang'       => 1,
    'scheduled_date' => '2026-10-15',
    'scheduled_time' => '09:30',
];

$ch = curl_init('https://api.turkeysms.com.tr/sms/send');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key":        "API_ANAHTARINIZ",
    "title":          "BASLIGINIZ",
    "sentto":         "905XXXXXXXXX",
    "text":           "Kampanyamız yarın başlıyor.",
    "sms_lang":       1,
    "scheduled_date": "2026-10-15",
    "scheduled_time": "09:30",
}

r = requests.post("https://api.turkeysms.com.tr/sms/send", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key:        'API_ANAHTARINIZ',
  title:          'BASLIGINIZ',
  sentto:         '905XXXXXXXXX',
  text:           'Kampanyamız yarın başlıyor.',
  sms_lang:       1,
  scheduled_date: '2026-10-15',
  scheduled_time: '09:30',
};

const r = await fetch('https://api.turkeysms.com.tr/sms/send', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();

Successful response

JSON — 200 OK
{
  "result": true,
  "result_code": "TS-1024",
  "result_message": "SMS dispatched successfully.",
  "sms_id": 734129955,
  "number_of_sms": 1,
  "total_recipients": 1,
  "success_count": 1,
  "sms_lang": "Turkish",
  "content_type": "Transactional",
  "country": "Turkey-TR"
}

For a scheduled send, sms_id is the send's report ID. Use this value as raporid in the Reports endpoints; SMS status query does not recognize this ID.

Error response

JSON — 400
{
  "result": false,
  "result_code": "TS-1072",
  "result_message": "Scheduled time must not be in the past."
}
CodeHTTPMeaning
TS-1070400scheduled_date is not in YYYY-MM-DD format or scheduled_time is missing.
TS-1071400scheduled_time is not in HH:MM or HH:MM:ss format.
TS-1072400The specified time is in the past or invalid (for example, 25:99).
TS-1060400The 50,000 number limit was exceeded.

The other codes are the same as in Sending SMS.

Group sending

Sends to many numbers with a single request. There are two endpoints:

  • /group/send: the same text to all numbers.
  • /group/sendMixed: each number gets its own text. text is an array whose order matches the number array.
POST https://api.turkeysms.com.tr/group/send
POST https://api.turkeysms.com.tr/group/sendMixed

Required permission: “Send to group” (API Center → My Keys → key → Scopes)

Parameters

ParameterTypeStatusDescription
api_keystringRequiredYour API key.
titlestringRequiredA Sender ID approved on your account.
senttoarrayRequiredAn array of numbers (a JSON array; comma-separated text is not accepted). At most 50,000 items. It can also be sent as numbers.
textstring / arrayRequired/group/send: a single text. /group/sendMixed: an array of texts with the same length as the number array; text[i] goes to the number sentto[i]. Each text is at most 2,000 characters; TS-L is converted to a line break.
sms_langintOptional0 English, 1 Turkish, 2 Arabic/Unicode. Default 2. For Turkish text, send 1.
scheduled_smsintOptionalIf 1 is sent, the send is scheduled.
scheduled_datestringConditionalRequired if scheduled_sms is 1. YYYY-MM-DD.
scheduled_timestringConditionalRequired if scheduled_sms is 1. HH:MM or HH:MM:ss. The date and time are evaluated in Türkiye time (Europe/Istanbul).

Rules

  • Numbers are converted with the same rules as /sms/send: spaces, +, - and parentheses are removed; the 00 is removed from numbers that start with 00; and 05… and 10-digit 5… numbers are converted to the 905… format. Numbers are not validated one by one.
  • In a /group/send request, a repeated number is queued once. In a /group/sendMixed request, an identical number and text pair is queued once; different texts to the same number are sent separately.
  • Your balance must cover the total SMS count (TS-1027).
  • Send the date and time fields only together with scheduled_sms: 1.
  • Your number blocking list is not applied on these endpoints (see Number blocking).
  • A successful response shows that the send has been queued; the messages are sent afterwards.

Request example (/group/send)

curl -X POST https://api.turkeysms.com.tr/group/send \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "title": "BASLIGINIZ",
  "sentto": [
    "905XXXXXXXXX",
    "905YYYYYYYYY"
  ],
  "text": "Mağazamız bugün 21:00'\''e kadar açıktır.",
  "sms_lang": 1
}'
$payload = [
    'api_key'  => 'API_ANAHTARINIZ',
    'title'    => 'BASLIGINIZ',
    'sentto'   => ['905XXXXXXXXX', '905YYYYYYYYY'],
    'text'     => 'Mağazamız bugün 21:00\'e kadar açıktır.',
    'sms_lang' => 1,
];

$ch = curl_init('https://api.turkeysms.com.tr/group/send');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key":  "API_ANAHTARINIZ",
    "title":    "BASLIGINIZ",
    "sentto":   ["905XXXXXXXXX", "905YYYYYYYYY"],
    "text":     "Mağazamız bugün 21:00'e kadar açıktır.",
    "sms_lang": 1,
}

r = requests.post("https://api.turkeysms.com.tr/group/send", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key:  'API_ANAHTARINIZ',
  title:    'BASLIGINIZ',
  sentto:   ['905XXXXXXXXX', '905YYYYYYYYY'],
  text:     'Mağazamız bugün 21:00\'e kadar açıktır.',
  sms_lang: 1,
};

const r = await fetch('https://api.turkeysms.com.tr/group/send', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();

Request example (/group/sendMixed)

curl -X POST https://api.turkeysms.com.tr/group/sendMixed \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "title": "BASLIGINIZ",
  "sentto": [
    "905XXXXXXXXX",
    "905YYYYYYYYY"
  ],
  "text": [
    "Sayın Ayşe Hanım, siparişiniz hazır.",
    "Sayın Mehmet Bey, siparişiniz hazır."
  ],
  "sms_lang": 1
}'
$payload = [
    'api_key'  => 'API_ANAHTARINIZ',
    'title'    => 'BASLIGINIZ',
    'sentto'   => ['905XXXXXXXXX', '905YYYYYYYYY'],
    'text'     => ['Sayın Ayşe Hanım, siparişiniz hazır.', 'Sayın Mehmet Bey, siparişiniz hazır.'],
    'sms_lang' => 1,
];

$ch = curl_init('https://api.turkeysms.com.tr/group/sendMixed');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key":  "API_ANAHTARINIZ",
    "title":    "BASLIGINIZ",
    "sentto":   ["905XXXXXXXXX", "905YYYYYYYYY"],
    "text":     ["Sayın Ayşe Hanım, siparişiniz hazır.", "Sayın Mehmet Bey, siparişiniz hazır."],
    "sms_lang": 1,
}

r = requests.post("https://api.turkeysms.com.tr/group/sendMixed", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key:  'API_ANAHTARINIZ',
  title:    'BASLIGINIZ',
  sentto:   ['905XXXXXXXXX', '905YYYYYYYYY'],
  text:     ['Sayın Ayşe Hanım, siparişiniz hazır.', 'Sayın Mehmet Bey, siparişiniz hazır.'],
  sms_lang: 1,
};

const r = await fetch('https://api.turkeysms.com.tr/group/sendMixed', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();

Successful response

JSON — 200 OK
{
  "result": true,
  "result_code": "TS-1024",
  "result_message": "Bulk SMS dispatched successfully.",
  "rapor_id": 482913377,
  "total_numbers": 2,
  "total_sms_cost": 2,
  "scheduled": false
}
FieldDescription
rapor_idThe send's report ID. Used as raporid in the Reports endpoints.
total_numbersThe number of numbers queued after conversion and de-duplication.
total_sms_costThe total SMS count.
scheduledtrue if the send was scheduled.

Error response

JSON — 403
{
  "result": false,
  "result_code": "TS-1028",
  "result_message": "Sender ID was not found in your account or is not approved."
}
CodeHTTPMeaning
TS-1033400The body is invalid, text is empty, text has the wrong type, or in /group/sendMixed the number of texts and numbers is not equal.
TS-1025400api_key is missing or shorter than 30 characters.
TS-1029400title is missing.
TS-1026400sentto is missing or not an array, or a text is longer than 2,000 characters.
TS-1060400The 50,000 number limit was exceeded.
TS-1070 / TS-1071 / TS-1072400The scheduling fields are invalid: date format, time format or a time in the past.
TS-1031403The key was not found or is not active.
TS-1067403The “Send to group” permission is off.
TS-1030403The account is not active.
TS-1028403The Sender ID was not found on your account or is not approved.
TS-1027403Insufficient balance.
SRV-ERR500Unexpected server error.

Sending OTP

Sends a verification code (OTP) generated by TurkeySMS to a single number. The text comes from a fixed template; the sender name is always OTPSMS.

POST https://api.turkeysms.com.tr/otp/send

Required permission: “Send OTP” (API Center → My Keys → key → Scopes)

Parameters

ParameterTypeStatusDescription
api_keystringRequiredYour API key.
mobilestringRequiredA single recipient number. Send it in the 905XXXXXXXXX format; the 05…, 5… and 00… formats are also converted.
digitsintOptionalCode length: 4, 5 or 6. Default 4; for any other value, 4 is used.
sms_langintOptionalTemplate language: 0 English, 1 Turkish, 2 Arabic. Default 2. It can also be sent as lang; if both are sent, sms_lang is used.

Templates

MARKA is the OTP brand name set on your account. In the example, the code is 4821.

sms_lang: 1 (Turkish)
4821
Aktivasyon kodunuz OTP
MARKA
sms_lang: 0 (English)
Your activation code is:4821
MARKA
sms_lang: 2 (Arabic)
4821
هو رمز التفعيل الخاص بك
MARKA

Request example

curl -X POST https://api.turkeysms.com.tr/otp/send \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "mobile": "905XXXXXXXXX",
  "digits": 6,
  "sms_lang": 1
}'
$payload = [
    'api_key'  => 'API_ANAHTARINIZ',
    'mobile'   => '905XXXXXXXXX',
    'digits'   => 6,
    'sms_lang' => 1,
];

$ch = curl_init('https://api.turkeysms.com.tr/otp/send');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key":  "API_ANAHTARINIZ",
    "mobile":   "905XXXXXXXXX",
    "digits":   6,
    "sms_lang": 1,
}

r = requests.post("https://api.turkeysms.com.tr/otp/send", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key:  'API_ANAHTARINIZ',
  mobile:   '905XXXXXXXXX',
  digits:   6,
  sms_lang: 1,
};

const r = await fetch('https://api.turkeysms.com.tr/otp/send', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();

Successful response

JSON — 200 OK
{
  "result": true,
  "result_code": "TS-1024",
  "result_message": "OTP dispatched successfully.",
  "sms_id": 48213390,
  "otp_code": 482193,
  "sandbox": false
}
FieldDescription
sms_idThe message ID; can be used with SMS status query.
otp_codeThe code that was sent. It is returned as an integer and does not start with 0.
sandboxfalse in live requests.
Verifying the code is your system's job. The API has no verification endpoint. Store the otp_code value on your server with a short validity period (for example, 3–5 minutes), compare it with the code the user enters, and delete it after use. Do not send the code to the client (browser, mobile app). On your side, limit how many requests can be made to the same number within a short time.

Error response

JSON — 403
{
  "result": false,
  "result_code": "TS-1036",
  "result_message": "OTP sending privilege is disabled for this API key."
}
CodeHTTPMeaning
TS-1033400The body is invalid.
TS-1050401api_key is missing or shorter than 30 characters.
TS-1025400mobile is missing or shorter than 7 characters.
TS-1034403The number format is invalid (it must be 11–15 digits after cleanup).
TS-1031403The key was not found or is not active.
TS-1036403The “Send OTP” permission is off.
TS-1030403The account is not active.
TS-1027403Insufficient balance.
TS-5000403The message could not be saved; retry the request.
SRV-ERR500Unexpected server error.

Advanced OTP

Sends an OTP with your own Sender ID and your own text. TurkeySMS generates the code and puts it in place of the TS-CODE token in the text.

POST https://api.turkeysms.com.tr/otp/detailed

Required permissions: “Allow POST requests” and “Advanced OTP” (API Center → My Keys → key → Scopes)

Parameters

ParameterTypeStatusDescription
api_keystringRequiredYour API key.
mobilestringRequiredA single recipient number, 905XXXXXXXXX.
titlestringRequiredA Sender ID on your account. At most 11 characters; Turkish characters count as two characters. The Sender ID must be approved, its document must be approved and the operator approval must be complete.
textstringRequiredMessage text; it must contain the TS-CODE token (in capitals). At most 2,000 characters. TS-L is converted to a line break.
langintOptional0 English, 1 Turkish, 2 Arabic/Unicode. Default 2. This endpoint does not read sms_lang; use lang.
digitsintOptionalCode length: 4, 5 or 6. Default 4.

Rules

  • The following Sender IDs are not accepted (case-insensitive): test, api, apikey, api_key, test123, 123, 0000, 123456789, senderid, sender, title, text, content.
  • Your balance must cover the SMS count of the text. The SMS count is calculated with the tables in Message language and SMS count.
  • Validation errors are returned with HTTP 400, and account and permission errors with HTTP 403.

Request example

curl -X POST https://api.turkeysms.com.tr/otp/detailed \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "mobile": "905XXXXXXXXX",
  "title": "BASLIGINIZ",
  "text": "Giriş kodunuz: TS-CODE. Kodu kimseyle paylaşmayın.",
  "lang": 1,
  "digits": 6
}'
$payload = [
    'api_key' => 'API_ANAHTARINIZ',
    'mobile'  => '905XXXXXXXXX',
    'title'   => 'BASLIGINIZ',
    'text'    => 'Giriş kodunuz: TS-CODE. Kodu kimseyle paylaşmayın.',
    'lang'    => 1,
    'digits'  => 6,
];

$ch = curl_init('https://api.turkeysms.com.tr/otp/detailed');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key": "API_ANAHTARINIZ",
    "mobile":  "905XXXXXXXXX",
    "title":   "BASLIGINIZ",
    "text":    "Giriş kodunuz: TS-CODE. Kodu kimseyle paylaşmayın.",
    "lang":    1,
    "digits":  6,
}

r = requests.post("https://api.turkeysms.com.tr/otp/detailed", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key: 'API_ANAHTARINIZ',
  mobile:  '905XXXXXXXXX',
  title:   'BASLIGINIZ',
  text:    'Giriş kodunuz: TS-CODE. Kodu kimseyle paylaşmayın.',
  lang:    1,
  digits:  6,
};

const r = await fetch('https://api.turkeysms.com.tr/otp/detailed', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();

Successful response

JSON — 200 OK
{
  "result": true,
  "result_code": "TS-1024",
  "result_message": "OTP dispatched successfully.",
  "sms_id": 48213391,
  "otp_code": 482193,
  "number_of_sms": 1,
  "sms_lang": "Turkish",
  "sandbox": false
}
FieldDescription
sms_idThe message ID.
otp_codeThe code that was sent. It is returned as an integer and does not start with 0.
number_of_smsThe SMS count of the message.
sms_langThe label of the lang value.
sandboxfalse in live requests.
If an error occurs during delivery to the operator, the response is still TS-1024. You can check the message status with SMS status query; on a delivery error it returns TS-1022 with the error description in the details field. The warning about verifying the code in Sending OTP also applies here.

Error response

JSON — 403
{
  "result": false,
  "result_code": "TS-1028",
  "result_message": "Sender ID is not approved for OTP (approval, document and network approval are required)."
}
CodeHTTPMeaning
TS-1033400The body is invalid, a field is not a string, or the Sender ID is on the not-accepted list.
TS-1050400api_key is missing.
TS-1025400mobile is missing.
TS-1051400title is missing.
TS-1026400text is empty, does not contain TS-CODE or is longer than 2,000 characters.
TS-1031400 403400: the key is shorter than 30 characters. 403: the key was not found or is not active.
TS-1034400The number format is invalid.
TS-1029400 403400: the Sender ID is longer than 11 characters. 403: the Sender ID was not found on your account.
TS-1061403The “Allow POST requests” permission is off.
TS-1037403The “Advanced OTP” permission is off.
TS-1030403The account is not active.
TS-1028403The Sender ID is not approved for OTP (approval, document or operator approval is missing).
TS-1027403Insufficient balance.
TS-5000403The message could not be saved; retry the request.
SRV-ERR500Unexpected server error.

Message language and SMS count

sms_lang (lang in Advanced OTP) sets the message's character set and how many SMS it counts as. A wrong value can cause the text to be split into more SMS.

ValueUse
0English; Latin letters and standard symbols only (no Turkish characters).
1Turkish; text that contains ç, ğ, ı, İ, ö, ş, ü.
2Arabic and other Unicode text. This is the default value.

SMS count

The numbers in the table are the maximum number of characters that fit in the given SMS count. The character count is calculated after TS-L is converted to a line break.

Text length up to1234567
Türkiye numbers, sms_lang 01603054556107609101070
Türkiye numbers, sms_lang 11552454455957408901040
Türkiye numbers, sms_lang 265127190250315380445
International numbers (all values)70130195260325390450
  • Texts longer than the last column count as 8 SMS.
  • /sms/send and /otp/detailed treat numbers that do not start with 905 as international numbers. /group/send and /group/sendMixed use the Türkiye table for all numbers.

Content label

content_type in a /sms/send request (0 Transactional, 1 High Quality, 2 Advertising) is returned only as a label in the response; it does not change the sending route or the price.

Groups

Creates, renames, deletes and lists the groups in your contacts. Each operation has its own permission.

On these endpoints, some failed results are returned with HTTP 200 (for example, TS-1082, TS-1086). Always evaluate the result with result and result_code.

Creating a group

POST https://api.turkeysms.com.tr/groups/create

Required permission: “Create group” (API Center → My Keys → key → Scopes)

ParameterTypeStatusDescription
api_keystringRequiredYour API key.
group_namestringRequiredGroup name. 2–50 characters; Turkish characters count as two characters. It must be unique among your active groups.
curl -X POST https://api.turkeysms.com.tr/groups/create \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "group_name": "Müşteriler"
}'
$payload = [
    'api_key'    => 'API_ANAHTARINIZ',
    'group_name' => 'Müşteriler',
];

$ch = curl_init('https://api.turkeysms.com.tr/groups/create');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key":    "API_ANAHTARINIZ",
    "group_name": "Müşteriler",
}

r = requests.post("https://api.turkeysms.com.tr/groups/create", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key:    'API_ANAHTARINIZ',
  group_name: 'Müşteriler',
};

const r = await fetch('https://api.turkeysms.com.tr/groups/create', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();
JSON — 200 OK
{
  "result": true,
  "result_code": "TS-1080",
  "result_message": "Group created successfully.",
  "group": {
    "id": 5412,
    "name": "Müşteriler",
    "created_at": "2026-10-01"
  }
}

Use the group.id value as group_id when adding numbers.

Renaming a group

POST https://api.turkeysms.com.tr/groups/edit

Required permission: “Edit group” (API Center → My Keys → key → Scopes)

ParameterTypeStatusDescription
api_keystringRequiredYour API key.
group_idintRequiredGroup ID.
new_namestringRequiredNew name. This endpoint does not check length or uniqueness; we recommend keeping the name 2–50 characters long and unique.
curl -X POST https://api.turkeysms.com.tr/groups/edit \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "group_id": 5412,
  "new_name": "VIP Müşteriler"
}'
$payload = [
    'api_key'  => 'API_ANAHTARINIZ',
    'group_id' => 5412,
    'new_name' => 'VIP Müşteriler',
];

$ch = curl_init('https://api.turkeysms.com.tr/groups/edit');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key":  "API_ANAHTARINIZ",
    "group_id": 5412,
    "new_name": "VIP Müşteriler",
}

r = requests.post("https://api.turkeysms.com.tr/groups/edit", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key:  'API_ANAHTARINIZ',
  group_id: 5412,
  new_name: 'VIP Müşteriler',
};

const r = await fetch('https://api.turkeysms.com.tr/groups/edit', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();
JSON — 200 OK
{
  "result": true,
  "result_code": "TS-1087",
  "result_message": "Group name updated successfully.",
  "new_name": "VIP Müşteriler"
}

Deleting a group

POST https://api.turkeysms.com.tr/groups/delete

Required permission: “Delete group” (API Center → My Keys → key → Scopes)

ParameterTypeStatusDescription
api_keystringRequiredYour API key.
group_idintRequiredGroup ID.

A deleted group is removed from the lists and cannot be used again. Numbers added to the group are not deleted.

curl -X POST https://api.turkeysms.com.tr/groups/delete \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "group_id": 5412
}'
$payload = [
    'api_key'  => 'API_ANAHTARINIZ',
    'group_id' => 5412,
];

$ch = curl_init('https://api.turkeysms.com.tr/groups/delete');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key":  "API_ANAHTARINIZ",
    "group_id": 5412,
}

r = requests.post("https://api.turkeysms.com.tr/groups/delete", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key:  'API_ANAHTARINIZ',
  group_id: 5412,
};

const r = await fetch('https://api.turkeysms.com.tr/groups/delete', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();
JSON — 200 OK
{
  "result": true,
  "result_code": "TS-1088",
  "result_message": "Group deleted successfully."
}

Listing groups

POST https://api.turkeysms.com.tr/groups/list

Required permission: “List groups” (API Center → My Keys → key → Scopes)

ParameterTypeStatusDescription
api_keystringRequiredYour API key.
searchstringOptionalSearch within the name. If empty, all groups are returned.
curl -X POST https://api.turkeysms.com.tr/groups/list \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "search": "Müşteri"
}'
$payload = [
    'api_key' => 'API_ANAHTARINIZ',
    'search'  => 'Müşteri',
];

$ch = curl_init('https://api.turkeysms.com.tr/groups/list');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key": "API_ANAHTARINIZ",
    "search":  "Müşteri",
}

r = requests.post("https://api.turkeysms.com.tr/groups/list", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key: 'API_ANAHTARINIZ',
  search:  'Müşteri',
};

const r = await fetch('https://api.turkeysms.com.tr/groups/list', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();
JSON — 200 OK
{
  "result": true,
  "result_code": "TS-1090",
  "result_message": "Group list retrieved successfully.",
  "groups_count": 2,
  "groups": [
    {
      "id": 5413,
      "name": "VIP Müşteriler",
      "date": "2026-10-01"
    },
    {
      "id": 5398,
      "name": "Müşteriler 2025",
      "date": "2026-04-02"
    }
  ]
}

Only groups that have not been deleted are returned, newest first. There is no pagination.

Response codes

JSON — 200 (failed result)
{
  "result": false,
  "result_code": "TS-1082",
  "result_message": "Group name already exists."
}
CodeHTTPMeaning
TS-1080 / TS-1087 / TS-1088 / TS-1090200Created / updated / deleted / listed.
TS-1033400The body is invalid.
TS-1050400api_key is missing.
TS-1031400The key is shorter than 30 characters, was not found or is not active.
TS-1081 / TS-1084 / TS-1085 / TS-1089400The related permission is off: create / edit / delete / list.
TS-1030400The account is not active.
TS-1082200An active group with this name already exists.
TS-1083400 200The group name is empty or outside 2–50 characters. In edit, it is also returned with 200 for an invalid group_id.
TS-1086400 200400: group_id is missing. 200: the group was not found, does not belong to you or has been deleted.
SRV-ERR500Unexpected server error.

Numbers

Adds a number to a group. The number is saved to your contacts together with a name and three extra fields.

POST https://api.turkeysms.com.tr/contacts/add

Required permission: “Add number” (API Center → My Keys → key → Scopes)

Parameters

ParameterTypeStatusDescription
api_keystringRequiredYour API key.
group_idintRequiredThe ID of the group the number is added to (see Groups).
gsm_numberstringRequiredA Türkiye mobile number. 905XXXXXXXXX, 05XXXXXXXXX, 5XXXXXXXXX, +905… and 00905… are accepted; it is saved as 905XXXXXXXXX.
namestringOptionalThe contact's name.
f_01, f_02, f_03stringOptionalExtra fields; free text for personalization.

Rules

  • Only Türkiye mobile numbers can be added.
  • There is no check for whether the same number was already added to the group; prevent duplicates on your side.
  • Your number blocking list is not checked on this endpoint.

Request example

curl -X POST https://api.turkeysms.com.tr/contacts/add \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "group_id": 5412,
  "gsm_number": "905XXXXXXXXX",
  "name": "Ayşe Yılmaz",
  "f_01": "İstanbul"
}'
$payload = [
    'api_key'    => 'API_ANAHTARINIZ',
    'group_id'   => 5412,
    'gsm_number' => '905XXXXXXXXX',
    'name'       => 'Ayşe Yılmaz',
    'f_01'       => 'İstanbul',
];

$ch = curl_init('https://api.turkeysms.com.tr/contacts/add');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key":    "API_ANAHTARINIZ",
    "group_id":   5412,
    "gsm_number": "905XXXXXXXXX",
    "name":       "Ayşe Yılmaz",
    "f_01":       "İstanbul",
}

r = requests.post("https://api.turkeysms.com.tr/contacts/add", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key:    'API_ANAHTARINIZ',
  group_id:   5412,
  gsm_number: '905XXXXXXXXX',
  name:       'Ayşe Yılmaz',
  f_01:       'İstanbul',
};

const r = await fetch('https://api.turkeysms.com.tr/contacts/add', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();

Successful response

JSON — 200 OK
{
  "result": true,
  "result_code": "TS-1100",
  "result_message": "Contact added successfully.",
  "group_id": 5412,
  "total_sent": 1,
  "total_added": 1,
  "total_failed": 0,
  "mobile": "905XXXXXXXXX"
}

mobile is the number that was saved. total_sent, total_added and total_failed summarize this single-number operation.

Error response

JSON — 200 (failed result)
{
  "result": false,
  "result_code": "TS-1101",
  "result_message": "Failed to add contact."
}
CodeHTTPMeaning
TS-1033400The body is invalid.
TS-1050400api_key is missing.
TS-1025400gsm_number is missing.
TS-1086400 200400: group_id is missing, or the group was not found or does not belong to you. 200: group_id is not a number or is not greater than zero.
TS-1031400The key was not found or is not active.
TS-1065400The “Add number” permission is off.
TS-1030400The account is not active.
TS-1101200The number is not a valid Türkiye mobile number.
SRV-ERR500Unexpected server error.

Number blocking

Adds numbers to your number blocking list and checks whether a number is on the list. The list is per account.

Scope: The number blocking list is currently applied only to sends made from the panel. It is not applied to API sends made with /sms/send, /otp/* and /group/*; if you send through the API, filter out blocked numbers on your side.
POST https://api.turkeysms.com.tr/blacklist/post/add
POST https://api.turkeysms.com.tr/blacklist/post/status

Required permission: “Block number” (API Center → My Keys → key → Scopes)

The permission is required for both endpoints. The old /blacklist/add and /blacklist/status URLs are not used (404).

Parameters

ParameterTypeStatusDescription
api_keystringRequiredYour API key.
numberstringRequiredTürkiye mobile number. The 905XXXXXXXXX, +90 5XX…, 0090 5XX…, 05XX… and 5XX… formats are accepted and converted to the 905XXXXXXXXX format.
The number is saved in the 905XXXXXXXXX format, with the same rule as the panel. Adding and querying compare the last 10 digits of the number, so 05321234567 and 905321234567 count as the same number. Landline and foreign numbers are not accepted (TS-1144).

Response format difference: these endpoints use the status field ("success" or "error") instead of result.

Adding a number

curl -X POST https://api.turkeysms.com.tr/blacklist/post/add \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "number": "905XXXXXXXXX"
}'
$payload = [
    'api_key' => 'API_ANAHTARINIZ',
    'number'  => '905XXXXXXXXX',
];

$ch = curl_init('https://api.turkeysms.com.tr/blacklist/post/add');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['status'] ?? '') === 'success') {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key": "API_ANAHTARINIZ",
    "number":  "905XXXXXXXXX",
}

r = requests.post("https://api.turkeysms.com.tr/blacklist/post/add", json=payload, timeout=30)
data = r.json()
if data.get("status") == "success":
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key: 'API_ANAHTARINIZ',
  number:  '905XXXXXXXXX',
};

const r = await fetch('https://api.turkeysms.com.tr/blacklist/post/add', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.status === 'success') {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();
JSON — 200 OK
{
  "status": "success",
  "result_code": "TS-1141",
  "result_message": "Number added to blacklist successfully"
}

Checking a number

curl -X POST https://api.turkeysms.com.tr/blacklist/post/status \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "number": "905XXXXXXXXX"
}'
$payload = [
    'api_key' => 'API_ANAHTARINIZ',
    'number'  => '905XXXXXXXXX',
];

$ch = curl_init('https://api.turkeysms.com.tr/blacklist/post/status');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['status'] ?? '') === 'success') {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key": "API_ANAHTARINIZ",
    "number":  "905XXXXXXXXX",
}

r = requests.post("https://api.turkeysms.com.tr/blacklist/post/status", json=payload, timeout=30)
data = r.json()
if data.get("status") == "success":
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key: 'API_ANAHTARINIZ',
  number:  '905XXXXXXXXX',
};

const r = await fetch('https://api.turkeysms.com.tr/blacklist/post/status', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.status === 'success') {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();
JSON — 200 OK (on the list)
{
  "status": "success",
  "result_code": "TS-1143",
  "result_message": "The phone number is currently in the blacklist.",
  "is_blocked": true,
  "block_date": "2026-10-01",
  "block_time": "14:30"
}
JSON — 200 OK (not on the list)
{
  "status": "success",
  "result_code": "TS-1142",
  "result_message": "The phone number is NOT in the blacklist.",
  "is_blocked": false
}

block_date and block_time are the date and time the number was added to the list. To view the list and remove numbers, use the Number Blocking page in the panel; the API has no remove endpoint.

Error response

JSON — 403
{
  "status": "error",
  "result_code": "TS-1065",
  "result_message": "Number blocking privilege is disabled for this API key."
}
CodeHTTPMeaning
TS-1141200The number was added to the list.
TS-1143 / TS-1142200The number is on the list / not on the list.
TS-1050403api_key is missing or the body is invalid.
TS-1031401The key is shorter than 20 characters, was not found or is not active, or the account is not active.
TS-1025400number is missing.
TS-1144400The number is not a valid Türkiye mobile number.
TS-1065403The “Block number” permission is off.
TS-1140400The number is already on your list.
TS-1033400An error occurred while saving; retry.
TS-404404Wrong path.

Balance query

Returns the SMS credit in your account.

POST https://api.turkeysms.com.tr/balance/

Required permission: “Check balance” (API Center → My Keys → key → Scopes)

The URL is written with a trailing slash: /balance/. The URL without the slash returns a redirect (301); some HTTP clients turn the POST request into GET on a redirect, and the request fails.

Parameters

ParameterTypeStatusDescription
api_keystringRequiredYour API key.

Request example

curl -X POST https://api.turkeysms.com.tr/balance/ \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ"
}'
$payload = [
    'api_key' => 'API_ANAHTARINIZ',
];

$ch = curl_init('https://api.turkeysms.com.tr/balance/');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key": "API_ANAHTARINIZ",
}

r = requests.post("https://api.turkeysms.com.tr/balance/", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key: 'API_ANAHTARINIZ',
};

const r = await fetch('https://api.turkeysms.com.tr/balance/', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();

Successful response

JSON — 200 OK
{
  "result": true,
  "result_code": "TS-1040",
  "result_message": "Balance retrieved successfully.",
  "balance_main": 1500
}

balance_main: SMS credit (integer).

Error response

JSON — 403
{
  "result": false,
  "result_code": "TS-1065",
  "result_message": "Balance inquiry privilege is disabled for this key."
}
CodeHTTPMeaning
TS-1033400The body is invalid.
TS-1050400api_key is missing.
TS-1025400api_key is shorter than 30 characters.
TS-1031403The key was not found or is not active.
TS-1065403The “Check balance” permission is off.
TS-1030403The account is not active.
SRV-ERR403 500Unexpected server error.

Sender ID query

Lists the Sender IDs approved on your account. In sends, put a Sender ID from this list in the title field.

POST https://api.turkeysms.com.tr/senderid/check

Required permission: “Check Sender ID” (API Center → My Keys → key → Scopes)

Parameters

ParameterTypeStatusDescription
api_keystringRequiredYour API key.

Request example

curl -X POST https://api.turkeysms.com.tr/senderid/check \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ"
}'
$payload = [
    'api_key' => 'API_ANAHTARINIZ',
];

$ch = curl_init('https://api.turkeysms.com.tr/senderid/check');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key": "API_ANAHTARINIZ",
}

r = requests.post("https://api.turkeysms.com.tr/senderid/check", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key: 'API_ANAHTARINIZ',
};

const r = await fetch('https://api.turkeysms.com.tr/senderid/check', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();

Successful response

JSON — 200 OK
{
  "result": true,
  "result_code": "TS-1040",
  "result_message": "Operation success.",
  "sender_ids_count": 2,
  "sender_ids": [
    {
      "id": 1288,
      "title": "BASLIGINIZ",
      "status": 1,
      "network_stat": 1
    },
    {
      "id": 1102,
      "title": "MAGAZA",
      "status": 1,
      "network_stat": 1
    }
  ]
}
FieldDescription
sender_ids_countThe number of Sender IDs in the list.
sender_ids[].idSender ID record ID.
sender_ids[].titleThe Sender ID; used as title in sends.
sender_ids[].statusAlways 1, because only approved Sender IDs are listed.
sender_ids[].network_statIts values are not defined yet; do not use it in your integration.

The list starts with the newest Sender ID. Sender IDs that are pending approval or rejected are not listed; if there is no approved Sender ID, sender_ids is an empty array.

Error response

JSON — 403
{
  "result": false,
  "result_code": "TS-1038",
  "result_message": "Sender ID inquiry privilege is disabled for this key."
}
CodeHTTPMeaning
TS-1033400The body is invalid.
TS-1050400api_key is missing.
TS-1031400 403400: the key is shorter than 30 characters. 403: the key was not found or is not active.
TS-1038403The “Check Sender ID” permission is off.
TS-1030403The account is not active.
SRV-ERR403 500Unexpected server error.

SMS status query

Returns the delivery status of a single message. Only messages sent from your own account can be queried.

POST https://api.turkeysms.com.tr/sms/status

Required permissions: “Allow POST requests” and “Check SMS status” (API Center → My Keys → key → Scopes)

Parameters

ParameterTypeStatusDescription
api_keystringRequiredYour API key.
sms_idintRequiredMessage ID: the sms_id in the response of an immediate /sms/send (single recipient), /otp/send or /otp/detailed.
The IDs of scheduled sends and group sends are report IDs; for these, use the Reports endpoints. In an immediate send to several recipients, sms_id belongs only to the last recipient. Instead of polling the delivery status, we recommend using Webhook.

Request example

curl -X POST https://api.turkeysms.com.tr/sms/status \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "sms_id": 48213377
}'
$payload = [
    'api_key' => 'API_ANAHTARINIZ',
    'sms_id'  => 48213377,
];

$ch = curl_init('https://api.turkeysms.com.tr/sms/status');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['result'] ?? false) === true) {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key": "API_ANAHTARINIZ",
    "sms_id":  48213377,
}

r = requests.post("https://api.turkeysms.com.tr/sms/status", json=payload, timeout=30)
data = r.json()
if data.get("result") is True:
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key: 'API_ANAHTARINIZ',
  sms_id:  48213377,
};

const r = await fetch('https://api.turkeysms.com.tr/sms/status', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.result === true) {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();

Successful response

JSON — 200 OK
{
  "result": true,
  "result_code": "TS-1064",
  "result_message": "Number received the message.",
  "sender_id": "BASLIGINIZ",
  "date_of_sending": "2026-10-01",
  "time_of_sending": "14:27:12",
  "sms_status": "Number received the message",
  "sms_balance": "1 SMS",
  "details": "OK",
  "operator": "Turkcell"
}
FieldDescription
result_codeTS-1064: the message was delivered. TS-1022: no delivery confirmation (the message could not be delivered or the delivery report has not arrived yet). Both are returned with result: true and HTTP 200.
sms_statusNumber received the message or The number did not receive the message.
sender_idThe Sender ID the message was sent with.
date_of_sending, time_of_sendingThe date and time the message was recorded.
sms_balanceThe SMS count of this message (not the account balance).
detailsThe operation result; on a delivery error, the error description.
operatorThe recipient's operator; may be empty if unknown.

Error response

JSON — 403
{
  "result": false,
  "result_code": "TS-1020",
  "result_message": "The data sent is incorrect."
}
CodeHTTPMeaning
TS-1033400The body is invalid.
TS-1050403api_key is missing.
TS-1052403sms_id is missing, is not a number or is not greater than zero.
TS-1031403The key was not found or is not active.
TS-1061403The “Allow POST requests” permission is off.
TS-1063403The “Check SMS status” permission is off.
TS-1030403The account is not active.
TS-1020403No message of yours was found with this ID.
SRV-ERR500Unexpected server error.

Reports

Returns reports for group and scheduled sends. There are two endpoints: the Summary report gives the send's counters, and the Detailed report gives the delivery status per number.

POST https://api.turkeysms.com.tr/reports/basic
POST https://api.turkeysms.com.tr/reports/detailed

Required permissions: “Allow POST requests” and “Check SMS status” (API Center → My Keys → key → Scopes)

The raporid value is the rapor_id in the /group/send and /group/sendMixed response, or the sms_id in a scheduled /sms/send response. These endpoints use the status field instead of result; a successful response has no result_message.

Parameters

ParameterTypeStatusDescription
api_keystringRequiredYour API key.
raporidintRequiredReport ID.
pageintOptionalDetailed report only: page number, 1 or greater. Default 1. Each page returns at most 500 records.

Summary report

curl -X POST https://api.turkeysms.com.tr/reports/basic \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "raporid": 482913377
}'
$payload = [
    'api_key' => 'API_ANAHTARINIZ',
    'raporid' => 482913377,
];

$ch = curl_init('https://api.turkeysms.com.tr/reports/basic');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['status'] ?? '') === 'success') {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key": "API_ANAHTARINIZ",
    "raporid": 482913377,
}

r = requests.post("https://api.turkeysms.com.tr/reports/basic", json=payload, timeout=30)
data = r.json()
if data.get("status") == "success":
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key: 'API_ANAHTARINIZ',
  raporid: 482913377,
};

const r = await fetch('https://api.turkeysms.com.tr/reports/basic', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.status === 'success') {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();
JSON — 200 OK
{
  "status": "success",
  "result_code": "TS-1064",
  "rapor_id": 482913377,
  "total_numbers": 100,
  "success_count": 90,
  "failed_count": 5,
  "pending_count": 5,
  "details": {
    "sending_date": "2026-10-01",
    "sending_time": "14:22:10",
    "report_status": "Currently in the process of being sent.",
    "sms_sender_id": "BASLIGINIZ",
    "invalid_numbers": 2,
    "blocked_numbers": 1,
    "last_update": "2026-10-01 14:30:00"
  }
}
FieldDescription
total_numbersThe number of numbers in the send.
success_countThe number of successful messages.
failed_countFailed messages, including invalid and blocked numbers.
pending_countMessages whose result is not known yet.
details.sending_date, details.sending_timeThe date and time the report was created (for a scheduled send, this is not the send time).
details.report_statusText that describes the report's processing state.
details.sms_sender_idThe Sender ID used in the send.
details.invalid_numbers, details.blocked_numbersThe number of invalid numbers and the number of blocked numbers.
details.last_updateThe time the counters were last updated.

The counters change as the sending and delivery report processes progress.

Detailed report

curl -X POST https://api.turkeysms.com.tr/reports/detailed \
  -H "Content-Type: application/json" \
  -d '{
  "api_key": "API_ANAHTARINIZ",
  "raporid": 482913377,
  "page": 1
}'
$payload = [
    'api_key' => 'API_ANAHTARINIZ',
    'raporid' => 482913377,
    'page'    => 1,
];

$ch = curl_init('https://api.turkeysms.com.tr/reports/detailed');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
    CURLOPT_POSTFIELDS     => json_encode($payload),
    CURLOPT_CONNECTTIMEOUT => 10,
    CURLOPT_TIMEOUT        => 30,
]);
$body = curl_exec($ch);
$http = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

$res = $body !== false ? json_decode($body, true) : null;
if (($res['status'] ?? '') === 'success') {
    // Request succeeded
} else {
    error_log('TurkeySMS: ' . $http . ' ' . ($res['result_code'] ?? 'no response'));
}
import requests

payload = {
    "api_key": "API_ANAHTARINIZ",
    "raporid": 482913377,
    "page":    1,
}

r = requests.post("https://api.turkeysms.com.tr/reports/detailed", json=payload, timeout=30)
data = r.json()
if data.get("status") == "success":
    print("Success", data)
else:
    print("Error", r.status_code, data.get("result_code"))
// Node.js 18+ (built-in fetch)
(async () => {
const payload = {
  api_key: 'API_ANAHTARINIZ',
  raporid: 482913377,
  page:    1,
};

const r = await fetch('https://api.turkeysms.com.tr/reports/detailed', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(payload),
});
const data = await r.json();
if (data.status === 'success') {
  console.log('Success', data);
} else {
  console.error('Error', r.status, data.result_code);
}
})();
JSON — 200 OK
{
  "status": "success",
  "result_code": "TS-1064",
  "data": [
    {
      "phone_number": "905XXXXXXXXX",
      "sent_at": "2026-10-01 14:22:10",
      "sms_status": "Number received the message",
      "details": {
        "done_at": "2026-10-01 14:22:15",
        "status_code": 1,
        "operator": "TURKCELL"
      }
    }
  ],
  "pagination": {
    "current_page": 1,
    "total_pages": 3,
    "total_records": 1203,
    "records_per_page": 500
  }
}
details.status_codesms_statusMeaning
1Number received the messageDelivered.
2Expiration timeThe validity period expired; not delivered.
0Number didn't receive the messageNot delivered, or the delivery report has not arrived yet.

details.operator values: TURKCELL, VODAFONE, TURKTELEKOM, KKTCELL, TELSIM, UNKNOWN. If there are no records yet (for example, if a scheduled send has not started), data is an empty array. An empty array is also returned for pages after the last page.

Error response

JSON — 200 (report not found)
{
  "status": "error",
  "result_code": "TS-1029",
  "result_message": "The Report ID is invalid or missing."
}
CodeHTTPMeaning
TS-1064200The report was returned.
TS-1029400 200400: raporid is missing or not a number. 200: the report was not found or does not belong to you.
TS-1033400 404400: the body or page is invalid. 404: wrong path.
TS-1050400api_key is missing.
TS-1031400The key is shorter than 30 characters, was not found or is not active.
TS-1061400The “Allow POST requests” permission is off.
TS-1063400The “Check SMS status” permission is off.
TS-1030400The account is not active.
SRV-ERR500Unexpected server error.

Webhooks

With webhooks, TurkeySMS notifies an address you choose about events on your account (a message being passed to the operator, a delivery report, an inbound SMS and so on) with an HTTP POST request. This way you do not need to keep querying the API (polling) to learn a message's status.

The request and body examples in this section are identical in format to what the live system sends; the values in them (numbers, IDs, times) are examples.

Migrating from the previous version: The fields described in the old version of this page are no longer used. Update your receiver according to the mapping below:
OldNew
X-TurkeySMS-Webhook-Id / event_idX-TurkeySMS-Delivery / id
X-TurkeySMS-Webhook-Version / versionRemoved
X-TurkeySms-Signature (body only)X-TurkeySMS-Signature-V2 (timestamped); the old header is still sent for compatibility
timestamp (ISO text)timestamp (Unix seconds, number) and created_at (ISO 8601)
generated_atRemoved
data.sms_iddata.message_id
data.mobiledata.to
data.delivered_atdata.done_at
data.failure_reasondata.reason
data.operatorRemoved (operator_status is the operator's status text)

How it works

  1. An event occurs on your account (for example, your message is passed to the operator or a delivery report arrives).
  2. TurkeySMS queues the event for your webhooks that have selected it and gives each event a unique id.
  3. The request body is prepared as JSON, signed with HMAC using your secret, and sent to your address with POST.
  4. If your server returns 2xx within 10 seconds, the event counts as delivered. On temporary errors (connection error, timeout, 408, 429, 5xx), the event is retried as many times as you configured.

Typical notification times for events:

EventWhen it is sentTypical delay
sms.sentWhen the message is passed to the operatorAbout 1.5–2 minutes (the system waits 90 seconds for the message record to be completed)
sms.deliveredWhen a successful delivery report arrives from the operatorWithin 30–60 seconds after the report arrives (if the report arrives very quickly, together with sms.sent, 1.5–2 minutes after sending)
sms.failedWhen the message could not be passed to the operator or could not be delivered to the recipientFor a delivery failure, 30–60 seconds after the report arrives; for a message that could not be passed to the operator, about 6–7 minutes after it was recorded
sms.receivedWhen a message reaches your inbound SMS number (0850)Usually within 30 seconds
inbound.matchedWhen the “Fire Webhook” action in an automation rule runsUsually within 30 seconds
key.testWhen you press the Test button in the panelThe moment you press the button (synchronous)
If the operator does not return a delivery report within 72 hours, sms.delivered or sms.failed is not sent for that message. You can always query a message's final status with SMS status query. The otp.verified event appears as “Coming soon” in the panel and is not sent at the moment.

Setup

Webhooks are managed in the My Account → API Center → Security & IP → Webhook tab. The fields in the “Add new webhook” form:

FieldDescription
KeyThe API key the webhook belongs to. Each API key has a single webhook; saving again for the same key replaces the existing webhook.
URLThe address events are sent to. Use a public https:// address (see Security).
Signing secretThe secret value used for the signature. It is not shown in the panel after it is saved. When editing, leaving the field empty keeps the current secret; enter a new value to change it, or tick the “Remove the current secret (send unsigned)” box to remove it. If there is no secret, requests are sent unsigned (we do not recommend this).
Signing algorithmsha256 (default) or sha512.
Maximum retries0–10. Default 3. The maximum number of retries after the first attempt.
Retry interval (seconds)1–3600. Default 30. It doubles with each attempt (see Retries).
Trigger eventsThe events you want to receive. If none is selected, all events except sms.received are sent. Because sms.received carries message content, it is sent only if it is selected explicitly.
Webhook activeWhile it is off, events are recorded but not sent; pending retries are also canceled. When you turn it back on, the events from the off period are not sent. The configuration is kept.
If you want to receive delivery reports and inbound SMS at different addresses, use two separate API keys: select sms.sent, sms.delivered, sms.failed on one key's webhook, and only sms.received on the other.

Which event goes to which webhook?

CaseWebhook that receives the event
Message sent with an API keyOnly that key's webhook. In the body, key_id is this key's number.
Message sent from the panel or by an automationAll enabled webhooks on your account that have selected the event. In the body, key_id = null.
sms.receivedAll enabled webhooks on your account that have explicitly selected the sms.received event. The message must arrive at a 0850 number assigned to your account.
inbound.matchedThe first enabled webhook on your account (the one with the lowest key number). It does not depend on the event selection.
key.testThe webhook of the key whose Test button you pressed.

Messages sent with your account's main API key are treated like panel sends (all webhooks, key_id = null). If two webhooks on your account use the same URL, each event is sent to that URL only once.

Request format

Each request is sent with the POST method and a UTF-8 JSON body. Headers:

HeaderDescription
Content-TypeAlways application/json.
User-AgentFixed value: TurkeySMS-Webhook/1.0.
X-TurkeySMS-EventEvent name; the same as event in the body.
X-TurkeySMS-DeliveryDelivery ID (32 hex characters). It is the same as id in the body and does not change on retries.
X-TurkeySMS-AttemptAttempt number: 1 on the first send, increased by one on each retry.
X-TurkeySMS-TimestampThe moment this attempt was sent (Unix seconds). Renewed on every attempt.
X-TurkeySMS-SignatureLegacy signature: <algo>=hex(HMAC(secret, body)). It does not cover the timestamp; it is sent only for backward compatibility.
X-TurkeySMS-Signature-V2Recommended signature: t=<time>,v1=hex(HMAC(secret, "<time>.<body>")). If no secret is set, the signature headers are not sent.
HTTP — example request headers
POST /turkeysms/webhook HTTP/1.1
Host: ornek.com
Content-Type: application/json
User-Agent: TurkeySMS-Webhook/1.0
X-TurkeySMS-Event: sms.delivered
X-TurkeySMS-Delivery: 27f38025030d060f3e3b760b318b406e
X-TurkeySMS-Attempt: 1
X-TurkeySMS-Timestamp: 1790857803
X-TurkeySMS-Signature: sha256=776ebd61c3789873d7851a30e868ff8e73dbc586bccd67d49e50b3498e827e44
X-TurkeySMS-Signature-V2: t=1790857803,v1=660a71ff372d5921231c4871913b030476acfdad1dafee4eb6f3117126837b11

The body uses the same envelope for every event:

FieldTypeDescription
idstringThe event's unique ID (32 hex characters) = X-TurkeySMS-Delivery. Use this value to filter out duplicates.
eventstringEvent name (for example, sms.delivered).
created_atstringThe time the event was created, ISO 8601.
timestampintThe time the event was created, Unix seconds. It does not change on retries; for the signature time window, use the t= value in the signature, not this.
dataobjectEvent-specific fields (below).

Events and fields

In the sms.sent, sms.delivered and sms.failed events, data contains the following common fields. The message text is not sent in these events.

FieldTypeDescription
message_idintMessage number. It is the same as sms_id in the /sms/send response; it can be used with SMS status query.
bulk_idstringThe send's bulk operation (campaign) number.
tostringRecipient number, in international format (905xxxxxxxxx).
sender_idstringThe Sender ID the message was sent with.
partsintHow many SMS parts the message consists of.
sourcestringThe message's source. Examples: api (through the API), Web (from the panel).
key_idint | nullThe number of the API key the message was sent with. null for messages sent from the panel or by an automation.
sent_atstringSend time, ISO 8601 (2026-10-01T15:28:31+03:00).

sms.sent — the message was passed to the operator. Only the common fields are sent.

JSON — sms.sent
{
    "id": "353272ae3ae66d3233cd5ebe30faadcf",
    "event": "sms.sent",
    "created_at": "2026-10-01T15:30:02+03:00",
    "timestamp": 1790857802,
    "data": {
        "message_id": 518431556,
        "bulk_id": "1259383818",
        "to": "905xxxxxxxxx",
        "sender_id": "BASLIGIM",
        "parts": 1,
        "source": "api",
        "key_id": 4804,
        "sent_at": "2026-10-01T15:28:31+03:00"
    }
}

sms.delivered — the message was delivered to the recipient. In addition to the common fields:

FieldTypeDescription
statusstringAlways delivered.
done_atstring | nullThe operator's delivery time, ISO 8601. null if the operator did not report a time.
operator_statusstringThe status text returned by the operator (for example, Message delivered to handset).
JSON — sms.delivered
{
    "id": "27f38025030d060f3e3b760b318b406e",
    "event": "sms.delivered",
    "created_at": "2026-10-01T15:30:03+03:00",
    "timestamp": 1790857803,
    "data": {
        "message_id": 518431556,
        "bulk_id": "1259383818",
        "to": "905xxxxxxxxx",
        "sender_id": "BASLIGIM",
        "parts": 1,
        "source": "api",
        "key_id": 4804,
        "sent_at": "2026-10-01T15:28:31+03:00",
        "done_at": "2026-10-01T15:28:33+03:00",
        "operator_status": "Message delivered to handset",
        "status": "delivered"
    }
}

sms.failed — the message could not be passed to the operator or could not be delivered. In addition to the common fields:

FieldTypeDescription
stagestringsubmit: the message has still not been passed to the operator about 6–7 minutes after it was recorded (in this case bulk_id is "0" and reason may be empty). delivery: the operator returned an undelivered report.
statusstringrejected at the submit stage; undelivered, expired or canceled at the delivery stage.
reasonstringError reason (at most 200 characters).
done_atstring | nullOnly at the delivery stage: the operator's report time.
operator_statusstringOnly at the delivery stage: the operator's status text.
JSON — sms.failed (stage: delivery)
{
    "id": "9755ae0b22863732063111ea35e2e1f4",
    "event": "sms.failed",
    "created_at": "2026-10-01T15:35:44+03:00",
    "timestamp": 1790858144,
    "data": {
        "message_id": 518431601,
        "bulk_id": "1259383818",
        "to": "905xxxxxxxxx",
        "sender_id": "BASLIGIM",
        "parts": 1,
        "source": "api",
        "key_id": 4804,
        "sent_at": "2026-10-01T15:28:31+03:00",
        "done_at": "2026-10-01T15:35:40+03:00",
        "operator_status": "Unknown Subscriber",
        "stage": "delivery",
        "status": "undelivered",
        "reason": "Unknown Subscriber"
    }
}
JSON — sms.failed (stage: submit)
{
    "id": "b1c2d3e4f5a60718293a4b5c6d7e8f90",
    "event": "sms.failed",
    "created_at": "2026-10-01T15:40:31+03:00",
    "timestamp": 1790858431,
    "data": {
        "message_id": 518431620,
        "bulk_id": "0",
        "to": "905xxxxxxxxx",
        "sender_id": "BASLIGIM",
        "parts": 1,
        "source": "api",
        "key_id": 4804,
        "sent_at": "2026-10-01T15:28:31+03:00",
        "stage": "submit",
        "status": "rejected",
        "reason": ""
    }
}

sms.received — an SMS arrived at the 0850 number assigned to your account. Because it contains the message text, it is sent only if it is selected explicitly in the webhook settings.

FieldTypeDescription
message_idintThe inbound message's number. Inbound messages are numbered separately from outbound messages.
fromstringThe sender's number.
tostringYour 0850 number the message arrived at (908509xxxxxx).
textstringMessage text (UTF-8).
networkstringThe sender's operator (for example, TURKCELL-TR).
received_atstringThe time the message was received, ISO 8601.
JSON — sms.received
{
    "id": "9262804e8760b90e1c0714d0ca78ac02",
    "event": "sms.received",
    "created_at": "2026-10-01T15:49:02+03:00",
    "timestamp": 1790858942,
    "data": {
        "message_id": 436956,
        "from": "905xxxxxxxxx",
        "to": "908509444004",
        "text": "BANK",
        "network": "TURKCELL-TR",
        "received_at": "2026-10-01T15:48:48+03:00"
    }
}

inbound.matched — the “Fire Webhook” action of a rule on the Inbound → Automation page ran. In addition to the same fields as sms.received, it contains rule_id (int) and rule_name (string).

In the inbound.matched event, received_at is currently sent in the YYYY-MM-DD HH:MM:SS format (Istanbul time, without time zone information). Accept both this format and the ISO 8601 format in your receiver.
JSON — inbound.matched
{
    "id": "4be0f6d1c2a3958e7f60a1b2c3d4e5f6",
    "event": "inbound.matched",
    "created_at": "2026-10-01T15:52:31+03:00",
    "timestamp": 1790859151,
    "data": {
        "message_id": 436957,
        "from": "905xxxxxxxxx",
        "to": "908509444004",
        "text": "HOOK",
        "network": "TURKCELL-TR",
        "received_at": "2026-10-01 15:52:24",
        "rule_id": 12,
        "rule_name": "Webhook'a ilet"
    }
}

key.test — sent with the Test button in the panel; it is not retried. The message value depends on your brand.

JSON — key.test
{
    "id": "5d0e3c9a7b1f42e68c0d9a1b2c3e4f50",
    "event": "key.test",
    "created_at": "2026-10-01T15:01:06+03:00",
    "timestamp": 1790856066,
    "data": {
        "key_id": 4804,
        "message": "TurkeySMS test fire",
        "test": true
    }
}
Events sent with the Webhook Simulator in the panel contain "simulated": true inside data. The message_id and phone numbers in these events are not real and there is no key_id field; check this field so you do not mix them with your live data.

Signature verification

If a secret is set, each request comes with two signature headers. Verify the X-TurkeySMS-Signature-V2 header; because it also covers the timestamp, it prevents a captured request from being sent again (replay).

  1. Read the request body raw (do not parse and rebuild the JSON; a single whitespace difference makes the signature invalid).
  2. Parse the X-TurkeySMS-Signature-V2 value: t=<unix>,v1=<hex>. Here v1 is the version label of the signature scheme.
  3. Determine the algorithm from the hex length: 64 characters = sha256, 128 characters = sha512 (or use the one you selected in the panel).
  4. Compute HMAC(algorithm, secret, t + "." + raw_body) as hex.
  5. Compare the result with the value in the signature using a constant-time comparison (hash_equals, crypto.timingSafeEqual, hmac.compare_digest).
  6. If |now − t| is greater than 300 seconds, reject the request. Make sure your server clock is synchronized with NTP.
  7. If verification fails, return 401 and do not process the body.
Do not check the time window with the timestamp field in the body. This field is the event's creation time and does not change on retries; it would make you reject a valid retry that arrives hours later. Each attempt is re-signed with a new t=.

Legacy signature (X-TurkeySMS-Signature): it has the sha256=<hex> format and signs only the body. Because it contains no timestamp, on its own it gives no protection against replay attacks. Do not use it in new integrations.

Changing the secret: As soon as the new secret is saved in the panel, all later attempts are signed with the new secret. To avoid downtime, first make your receiver accept both the old and the new secret, then change the secret in the panel, and a few hours later remove the old secret from your receiver.

Response, timeout and retries

Your server's responseWhat TurkeySMS does
2xxThe event was delivered; it is not sent again.
408, 429, 5xxTemporary error; it is retried.
Connection error, DNS error, TLS error, 10-second timeoutTemporary error; it is retried.
3xx (redirect)Redirects are not followed; permanent error, not retried. Use the URL's final address.
Other 4xx (400, 401, 403, 404 …)Permanent error; not retried.

The connection time is at most 5 seconds, and the total request time is at most 10 seconds. The wait time doubles with each attempt: interval × 2(attempt − 1), at most 6 hours. With the default settings (3 retries, 30 seconds):

AttemptWhenNote
1When the event is created
2About 30 s after attempt 1
3About 60 s after attempt 2
4About 120 s after attempt 3Last attempt; if it fails, the event is marked as exhausted.

Retries run on a 30-second processing cycle; the actual time may be up to about 30 seconds longer than in the table. All attempts (request, response code, the first 8,000 characters of the response body, duration) appear on the Webhook Center → Delivery Logs page. For this reason, do not return secret information in the response body; a short value (ok) is enough.

Respond first, process later: write the event to a database or a queue and return 200 right away. Do long operations (email, external API calls and so on) after the response. A response that takes longer than 10 seconds counts as a timeout even if your server processed the event, and the event is sent again.

Duplicates and ordering

  • At-least-once delivery: The same event may arrive more than once (for example, a retry after a timeout). Store the id value from the body, and if an id you have already processed arrives, return 200 without processing it.
  • Ordering is not guaranteed: Because of retries, sms.delivered may arrive before sms.sent. Keep the message status per message_id; delivered and failed are final states, and an sms.sent that arrives later must not change them.
  • Processing error: If you could not save the event, roll back your id record and return 500; the event is retried.
  • Events you do not recognize: New event types may be added in the future. Return 200 also when an event you do not handle arrives; otherwise unnecessary retries occur.

Security

  • Use HTTPS. TurkeySMS verifies the certificate; with an invalid, expired or self-signed certificate, the request fails.
  • Public address: localhost, private networks (10.x, 172.16–31.x, 192.168.x), CGNAT and reserved addresses are not accepted. URLs that contain a private or reserved IP address or a local name (localhost, .local, .lan, .internal) are rejected when saved; domain names that resolve to a private address are rejected at send time, and this error is not retried. The domain name is resolved on every send, and the connection is made to the verified IP address.
  • No redirects: Redirects such as http → https or adding a trailing / are not followed; enter the final address directly.
  • Reject unsigned requests: If you have set a secret, reject every request that has no signature header or cannot be verified with 401.
  • Do not rely on IP addresses: The source IP addresses of requests may change. Use the signature for verification instead of an IP list.
  • Protect the secret: Store it in an environment variable or a secret configuration file, not in the source code; if you suspect it has leaked, change it in the panel.
  • Do not return error details: On an error, return only the status code; stack traces or system information must not be written to the response body.
  • Personal data: sms.received and inbound.matched contain the message text and phone number; store this data in line with KVKK (the Turkish Personal Data Protection Law) and limit access to it.

Testing

ToolWhereWhat it does
Test buttonSecurity & IP → Webhook → Configured webhooksSends a key.test event and shows the result (HTTP code, duration) immediately. It is not retried.
Webhook SimulatorThe “Sandbox” card at the bottom of the same pageSends the event you choose (sms.sent, sms.delivered, sms.failed, sms.received, key.test) with a real signature and real retries; the body contains "simulated": true. If the webhook has not selected that event or is not active, nothing is sent. When “Dry-run” is ticked, no request is sent; only the body and the signature are shown.
Delivery LogsAPI Center → Webhook CenterShows the request and response of each attempt, the attempt number, the duration and the error reason; can be filtered by key, event and status, and downloaded as CSV.
Health & AlertsAPI Center → Webhook CenterThe number of consecutive failures, the success rate for the last 24 hours, the last success/failure time, risky webhooks and the retry status.
Your local development environment (localhost) cannot be used directly. Test through a public test server or an HTTPS tunnel service.

Example receivers

All three examples do the same job: they verify the V2 signature and the time window, filter out duplicates by id, and return 200 quickly. The examples have been tested with the valid request, retry, wrong secret, stale timestamp, tampered body and sha512 scenarios. For the duplicate check, you can also use a UNIQUE column in a database instead of a file; for critical operations, save the id in the same database transaction as the event.

<?php
// TurkeySMS webhook receiver (PHP 7.4+)
$secret  = getenv('TURKEYSMS_WEBHOOK_SECRET') ?: '';  // the secret you set in the panel (from an environment variable)
$seenDir = '/var/lib/myapp/webhook-seen';             // a writable folder outside the web root
$body    = file_get_contents('php://input');          // the signature is computed over the RAW body
if ($secret === '') {                                 // configuration missing: let TurkeySMS retry
    http_response_code(500);
    exit;
}

// 1) Verify X-TurkeySMS-Signature-V2: t=<unix>,v1=<hex HMAC("<t>.<body>")>
$sig = $_SERVER['HTTP_X_TURKEYSMS_SIGNATURE_V2'] ?? '';
if (!preg_match('/^t=(\d+),v1=([0-9a-f]{64}|[0-9a-f]{128})$/D', $sig, $m)) {
    http_response_code(401);
    exit;
}
$algo     = strlen($m[2]) === 64 ? 'sha256' : 'sha512';   // the algorithm you selected in the panel
$expected = hash_hmac($algo, $m[1] . '.' . $body, $secret);
// The time window is measured with t= in the signature (each attempt is re-signed), not with "timestamp" in the body.
if (!hash_equals($expected, $m[2]) || abs(time() - (int)$m[1]) > 300) {
    http_response_code(401);
    exit;
}

// 2) Filter out duplicates: "id" = X-TurkeySMS-Delivery, the same on every attempt.
$evt = json_decode($body, true);
$id  = is_array($evt) && preg_match('/^[0-9a-f]{32}$/D', (string)($evt['id'] ?? '')) ? $evt['id'] : '';
if ($id === '') {
    http_response_code(400);
    exit;
}
if (!is_dir($seenDir)) @mkdir($seenDir, 0750, true);
$marker = @fopen($seenDir . '/' . $id, 'x');              // fails if the same id arrived before
if ($marker === false) {
    http_response_code(is_file($seenDir . '/' . $id) ? 200 : 500);   // 500 → TurkeySMS retries
    exit;
}
fclose($marker);

// 3) Save or queue the event quickly (TurkeySMS waits at most 10 seconds).
//    If saving fails, delete the marker file and return 500; the event is retried.
$d = $evt['data'];
switch ($evt['event']) {
    case 'sms.sent':        /* $d['message_id'], $d['to'], $d['sender_id'] */ break;
    case 'sms.delivered':   /* $d['message_id'], $d['done_at'] */ break;
    case 'sms.failed':      /* $d['message_id'], $d['stage'], $d['status'], $d['reason'] */ break;
    case 'sms.received':    /* $d['from'], $d['to'], $d['text'] */ break;
    case 'inbound.matched': /* $d['from'], $d['text'], $d['rule_id'] */ break;
    case 'key.test':        break;
}

http_response_code(200);
echo 'ok';
// TurkeySMS webhook receiver (Node.js 18+ · Express 4)
const express = require('express');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

const SECRET = process.env.TURKEYSMS_WEBHOOK_SECRET;   // the secret you set in the panel
const SEEN_DIR = '/var/lib/myapp/webhook-seen';        // a writable, persistent folder
fs.mkdirSync(SEEN_DIR, { recursive: true });

const app = express();

// The signature is computed over the RAW body: do not use a JSON parser on this route.
app.post('/turkeysms/webhook', express.raw({ type: '*/*', limit: '256kb' }), (req, res) => {
  if (!SECRET) return res.sendStatus(500);              // configuration missing: let TurkeySMS retry
  const body = req.body;                                // Buffer
  const m = /^t=(\d+),v1=([0-9a-f]{64}|[0-9a-f]{128})$/.exec(req.get('X-TurkeySMS-Signature-V2') || '');
  if (!m) return res.sendStatus(401);

  const algo = m[2].length === 64 ? 'sha256' : 'sha512';
  const expected = crypto.createHmac(algo, SECRET).update(m[1] + '.').update(body).digest('hex');
  const fresh = Math.abs(Date.now() / 1000 - Number(m[1])) <= 300;
  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(m[2])) || !fresh) {
    return res.sendStatus(401);
  }

  let evt;
  try { evt = JSON.parse(body.toString('utf8')); } catch { return res.sendStatus(400); }
  if (!/^[0-9a-f]{32}$/.test(evt.id || '')) return res.sendStatus(400);

  // Filter out duplicates: "id" is the same on every attempt.
  try {
    fs.writeFileSync(path.join(SEEN_DIR, evt.id), '', { flag: 'wx' });
  } catch (e) {
    return res.sendStatus(e.code === 'EEXIST' ? 200 : 500);
  }

  // Save or queue the event quickly (at most 10 seconds).
  const d = evt.data;
  switch (evt.event) {
    case 'sms.sent':        /* d.message_id, d.to */ break;
    case 'sms.delivered':   /* d.message_id, d.done_at */ break;
    case 'sms.failed':      /* d.stage, d.status, d.reason */ break;
    case 'sms.received':    /* d.from, d.to, d.text */ break;
    case 'inbound.matched': /* d.text, d.rule_id */ break;
    case 'key.test':        break;
  }
  res.status(200).send('ok');
});

app.listen(3000);
# TurkeySMS webhook receiver (Python 3.8+ · Flask 2+)
import hashlib, hmac, json, os, re, time
from flask import Flask, request

SECRET = os.environ["TURKEYSMS_WEBHOOK_SECRET"].encode()   # the secret you set in the panel
SEEN_DIR = "/var/lib/myapp/webhook-seen"                     # a writable, persistent folder
os.makedirs(SEEN_DIR, exist_ok=True)
SIG_RE = re.compile(r"t=(\d+),v1=([0-9a-f]{64}|[0-9a-f]{128})")

app = Flask(__name__)

@app.post("/turkeysms/webhook")
def turkeysms_webhook():
    body = request.get_data()                               # RAW body (bytes)
    m = SIG_RE.fullmatch(request.headers.get("X-TurkeySMS-Signature-V2", ""))
    if not m:
        return "", 401
    ts, sig = m.group(1), m.group(2)
    algo = hashlib.sha256 if len(sig) == 64 else hashlib.sha512
    expected = hmac.new(SECRET, ts.encode() + b"." + body, algo).hexdigest()
    if not hmac.compare_digest(expected, sig) or abs(time.time() - int(ts)) > 300:
        return "", 401

    try:
        evt = json.loads(body)
    except ValueError:
        return "", 400
    eid = evt.get("id", "") if isinstance(evt, dict) else ""
    if not re.fullmatch(r"[0-9a-f]{32}", eid):
        return "", 400

    # Filter out duplicates: "id" is the same on every attempt.
    try:
        os.close(os.open(os.path.join(SEEN_DIR, eid), os.O_CREAT | os.O_EXCL | os.O_WRONLY))
    except FileExistsError:
        return "duplicate", 200
    except OSError:
        return "", 500                                      # TurkeySMS retries

    # Save or queue the event quickly (at most 10 seconds).
    d = evt["data"]
    if evt["event"] == "sms.delivered":
        pass  # d["message_id"], d["done_at"]
    elif evt["event"] == "sms.failed":
        pass  # d["stage"], d["status"], d["reason"]
    elif evt["event"] == "sms.received":
        pass  # d["from"], d["to"], d["text"]
    return "ok", 200

Troubleshooting

SymptomPossible causeFix
401 in Delivery LogsThe secret is not the same as in the receiver; the body was changed before the signature was verified (the JSON was parsed and rebuilt); the server clock has drifted.Enter the secret again on both sides; compute the signature over the raw body; synchronize the clock with NTP.
“Timeout”The receiver does not respond within 10 seconds.Return 200 first and do the work afterwards. The event is sent again; filter out the duplicate by id.
3xx and no retryThe URL redirects (for example, http → https, trailing /).Enter the final address, with no redirect, as the webhook URL.
No events arriveThe webhook is not active; the event is not selected; the message was sent with another API key; the URL is on a private network.Check the settings and the routing rules; verify the connection with the Test button.
sms.received does not arriveThe event was not selected explicitly (an empty selection does not include this event), or the message arrived at a number that is not assigned to your account.Tick the sms.received event on the webhook; send the message to a number on the Inbound → My Numbers page.
inbound.matched arrived at an unexpected URLThis event always goes to the first enabled webhook on your account.Make sure your first webhook can handle this event (if it does not process it, it should still return 200).
The same event arrived twiceAt-least-once delivery; a retry after a timeout.This is normal. Filter out duplicates by id.
sms.delivered did not arriveThe event is not selected, or the operator did not return a report within 72 hours.Check the webhook events; query the status with SMS status query.
Events arrive lateWhen your endpoint returns a connection error, 429 or 5xx, all pending events of that webhook are delayed by 60 seconds; if the error continues, the attempts are exhausted.Review the error on the Delivery Logs and Health & Alerts pages and fix your endpoint.

Go-live checklist

  • ☐ The webhook URL is a public https:// address with a valid certificate and does not redirect.
  • ☐ A secret is set; the receiver verifies the X-TurkeySMS-Signature-V2 signature over the raw body with a constant-time comparison.
  • ☐ The time window is checked with t= in the signature (300 s), and the server clock is synchronized with NTP.
  • ☐ Duplicates are filtered out by id; the status is kept per message_id, and final states are preserved.
  • ☐ The receiver returns 2xx in less than 10 seconds; long jobs are queued.
  • ☐ 200 is also returned for unrecognized events.
  • ☐ The right events are selected; if sms.received is needed, it is ticked explicitly.
  • ☐ All event types were tried with the Test button and the Webhook Simulator; there are no errors in Delivery Logs.

Frequently asked questions

Can I send events to more than one URL?
Yes. Each API key has one webhook; use different keys for different URLs. Events for messages sent from the panel go to all enabled webhooks.

Which time zone are events in?
Date fields are sent in ISO 8601 format with time zone information (+03:00); for received_at in inbound.matched, see the note above. timestamp and X-TurkeySMS-Timestamp are Unix seconds.

Is the message text included in the webhook?
Not in outbound message events (sms.sent, sms.delivered, sms.failed). It is included for inbound messages (sms.received, inbound.matched).

Can I receive events from the period when the webhook was off?
No. Events from the off period are not sent; query the message statuses for that period with SMS reports.

What happens when the retries are exhausted?
The event is marked as exhausted and is not sent again. You can see it in Delivery Logs.

Monitoring

You can monitor your API usage in the panel:

PanelContent
API Center → StatisticsTotal calls, success rate, distribution by endpoint and the most used keys.
API Center → Connection LogsRequest records; can be filtered by status, endpoint, key, IP and response code.
Webhook / Logs → Delivery LogsDelivery records of webhook requests and their responses.

Some requests rejected during parameter validation (for example, a missing field) may not appear in the connection logs. When debugging, we recommend that you also log requests and responses (excluding the API key) on your side.

Response codes

Codes may be returned with different HTTP statuses depending on the endpoint. Detailed descriptions are in the related endpoint section; this table is for quick reference.

Common

CodeHTTPMeaning
SRV-ERR500Unexpected server error. Balance query and Sender ID query may also return this code with 403.
TS-1033400 404Invalid body; in Groups, Numbers and Reports, wrong path with 404.
TS-1030400 403The account is not active.
TS-1031400 401 403The API key is invalid, was not found or is not active.
TS-1035403The key is paused, expired or revoked.
TS-1066403The request's IP address is not on the key's allowlist.
TS-1068429The key's hourly request limit has been reached.
TS-1069429The key's daily request limit has been reached.
TS-1073429The key's monthly request limit has been reached.

Key check

CodeHTTPMeaning
TS-1000200The key is valid; permissions and the account summary were returned.
TS-5000500Unexpected server error.

Sending SMS and scheduled sending

CodeHTTPMeaning
TS-1024200The send was accepted.
TS-1050401api_key is missing or short.
TS-1025400Recipient is missing.
TS-1051400Sender ID is missing.
TS-1029400Sender ID is longer than 11 characters.
TS-1026400Text is empty or longer than 2,000 characters.
TS-1028400The Sender ID is not on the account or is not approved.
TS-1060400 429400: recipient count limit. 429: per-minute send limit.
TS-1061403“Allow POST requests” is off.
TS-1062403“Send SMS” is off.
TS-1027403Insufficient balance.
TS-1070 / TS-1071 / TS-1072400Scheduling date, time or a time in the past.

Group sending

CodeHTTPMeaning
TS-1024200The send was queued.
TS-1025400api_key is missing or short.
TS-1029400Sender ID is missing.
TS-1026400The number list is missing or a text is longer than 2,000 characters.
TS-106040050,000 number limit.
TS-1070 / TS-1071 / TS-1072400The scheduling fields are invalid.
TS-1067403“Send to group” is off.
TS-1028403The Sender ID is not on the account or is not approved.
TS-1027403Insufficient balance.

Sending OTP and Advanced OTP

CodeHTTPMeaning
TS-1024200The OTP was sent.
TS-1050400 401api_key is missing.
TS-1025400Number is missing.
TS-1034400 403The number format is invalid.
TS-1051400Sender ID is missing (Advanced OTP).
TS-1026400Text is empty, has no TS-CODE or is too long (Advanced OTP).
TS-1029400 403Sender ID is longer than 11 characters or not on the account (Advanced OTP).
TS-1028403The Sender ID is not approved for OTP (Advanced OTP).
TS-1036403“Send OTP” is off.
TS-1037403“Advanced OTP” is off.
TS-1061403“Allow POST requests” is off (Advanced OTP).
TS-1027403Insufficient balance.
TS-5000403The message could not be saved; retry.

Groups

CodeHTTPMeaning
TS-1080 / TS-1087 / TS-1088 / TS-1090200Created / updated / deleted / listed.
TS-1050400api_key is missing.
TS-1081 / TS-1084 / TS-1085 / TS-1089400The related permission is off.
TS-1082200The group name already exists (failed result).
TS-1083400 200The group name is invalid.
TS-1086400 200The group was not found or group_id is missing.

Numbers

CodeHTTPMeaning
TS-1100200The number was added.
TS-1101200The number is invalid (failed result).
TS-1050400api_key is missing.
TS-1025400gsm_number is missing.
TS-1065400“Add number” is off.
TS-1086400 200The group was not found or group_id is invalid.

Number blocking

CodeHTTPMeaning
TS-1141200The number was added to the list.
TS-1142 / TS-1143200The number is not on the list / on the list.
TS-1050403api_key is missing.
TS-1025400number is missing.
TS-1144400The number is not a valid Türkiye mobile number.
TS-1140400The number is already on the list.
TS-1065403“Block number” is off.
TS-404404Wrong path.

Balance query and Sender ID query

CodeHTTPMeaning
TS-1040200The query succeeded.
TS-1050400api_key is missing.
TS-1025400api_key is short (Balance query).
TS-1065403“Check balance” is off.
TS-1038403“Check Sender ID” is off.

SMS status query

CodeHTTPMeaning
TS-1064200The message was delivered.
TS-1022200No delivery confirmation (not delivered or the report has not arrived yet).
TS-1050403api_key is missing.
TS-1052403sms_id is missing or invalid.
TS-1061403“Allow POST requests” is off.
TS-1063403“Check SMS status” is off.
TS-1020403The message was not found.

Reports

CodeHTTPMeaning
TS-1064200The report was returned.
TS-1029400 200The report ID is missing or the report was not found.
TS-1050400api_key is missing.
TS-1061400“Allow POST requests” is off.
TS-1063400“Check SMS status” is off.

SDKs and integrations

The examples on this page need no library; they work with the standard HTTP client in each language. Set a timeout in your own code and evaluate the result by the result (or status) and result_code fields.

Ready-made integrations

Interactive documentation

To try the API from your browser, you can use the interactive documentation (Swagger, OpenAPI 3.1).

Requests sent from the interactive documentation go to the live system; on send endpoints, real SMS messages are sent.

Open the interactive documentation

Support