International SMS
The fifteen endpoints that manage international SMS sender name applications, country prices and module settings.
Overview
Some countries insist the SMS sender name is approved in advance. The client applies with documents and you approve or refuse; the first six endpoints run that flow.
The rest is about the selling: which module sends, the cost and price per country, the margin, and clearing old records. Prices are kept in a single primary currency, and automatic pricing does not run until it is set.
Reference
Listing the Applications
Returns the sender name applications clients opened, one per country.
waiting is awaiting review, active was approved, inactive was refused.curl 'https://panel.example.com/api/v1/admin/products/sms/intl-origins' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/sms/intl-origins', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/sms/intl-origins');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$response = Api::Products()->GetSmsIntlOrigins();
$waiting = array_filter(
$response['data'],
fn (array $r): bool => $r['status'] === 'waiting',
);{
"data": [
{
"id": 9,
"origin_id": 1,
"origin_name": "ACME",
"country_code": "sv",
"status": "waiting",
"status_message": "",
"client": { "id": 2, "full_name": "John Doe", "company_name": "" },
"attachments": [
{
"size": 255435,
"file_name": "licence.jpg",
"name": "215d8151c44f2d058eee5e5.jpg",
"file_path": "215d8151c44f2d058eee5e5.jpg"
}
],
"created_at": "2026-03-09 11:52:45",
"approved_date": null,
"rejected_date": null
}
],
"meta": { "total": 2, "page": 1, "limit": 25, "next_page": 0 }
}Application Detail
Returns one application. The schema is the same as a list item.
waiting is awaiting review, active was approved, inactive was refused.curl 'https://panel.example.com/api/v1/admin/products/sms/intl-origins/9' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/sms/intl-origins/9', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/sms/intl-origins/9');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$response = Api::Products()->GetSmsIntlOrigin(['id' => 9]);Settling an Application
Approves or refuses the application.
active approves, inactive refuses.active nor inactive.curl -X PUT 'https://panel.example.com/api/v1/admin/products/sms/intl-origins/9/status' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"action":"inactive","reason":"Documents missing"}'const res = await fetch('https://panel.example.com/api/v1/admin/products/sms/intl-origins/9/status', {
method: 'PUT',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ action: 'inactive', reason: 'Documents missing' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/sms/intl-origins/9/status');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'action' => 'inactive',
'reason' => 'Documents missing',
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Take the canned reason from the template list so the client always sees the same text.
$reasons = Api::Products()->GetSmsReasons([], ['group' => 'international-sms'])['data'];
Api::Products()->SetSmsIntlOriginStatus([
'id' => 9,
'action' => 'inactive',
'reason' => $reasons['en'][0]['description'] ?? '',
]);Settling in Bulk
Approves or refuses several applications in one call.
active or inactive.ids was empty.active nor inactive.curl -X POST 'https://panel.example.com/api/v1/admin/products/sms/intl-origins/bulk' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"ids":[9,10],"action":"active"}'const res = await fetch('https://panel.example.com/api/v1/admin/products/sms/intl-origins/bulk', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ ids: [9, 10], action: 'active' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/sms/intl-origins/bulk');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['ids' => [9, 10], 'action' => 'active']),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The bulk endpoint has NO reason field: settle one by one when a refusal message is needed.
Api::Products()->BulkSmsIntlOrigins([
'ids' => [9, 10],
'action' => 'active',
]);Deleting an Application
Deletes the application and the documents uploaded with it.
curl -X DELETE 'https://panel.example.com/api/v1/admin/products/sms/intl-origins/9' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/sms/intl-origins/9', {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/sms/intl-origins/9');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$response = Api::Products()->DeleteSmsIntlOrigin(['id' => 9]);A Delivery Report
Asks the provider for the live delivery state of a sent message.
curl 'https://panel.example.com/api/v1/admin/products/sms/intl-reports/501' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/sms/intl-reports/501', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/sms/intl-reports/501');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The report is produced by asking the provider: it can be slow and depends on module support.
$response = Api::Products()->GetSmsIntlReport(['id' => 501]);Listing the Refusal Reasons
Returns the canned texts used when refusing an application, one set per language.
sms or international-sms. It defaults to sms, so forgetting the parameter gives you the domestic list.curl -G 'https://panel.example.com/api/v1/admin/products/sms/reasons' \
-H "Authorization: Bearer $API_KEY" \
-d group=international-smsconst url = new URL('https://panel.example.com/api/v1/admin/products/sms/reasons');
url.searchParams.set('group', 'international-sms');
const res = await fetch(url, { headers: { Authorization: `Bearer ${apiKey}` } });
const body = await res.json();$url = 'https://panel.example.com/api/v1/admin/products/sms/reasons?' . http_build_query(['group' => 'international-sms']);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$response = Api::Products()->GetSmsReasons([], ['group' => 'international-sms']);Adding a Refusal Reason
Adds a canned refusal text to one language.
sms or international-sms. It defaults to sms, so forgetting the parameter gives you the domestic list.201. Same shape as the listing: one key per installed language, each holding its own list. A language with no reasons comes back as an empty array.curl -X POST 'https://panel.example.com/api/v1/admin/products/sms/reasons?group=international-sms' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"lang":"en","reason":"Please upload a valid trade licence."}'const res = await fetch('https://panel.example.com/api/v1/admin/products/sms/reasons?group=international-sms', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
lang: 'en',
reason: 'Please upload a valid trade licence.',
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/sms/reasons?group=international-sms');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'lang' => 'en',
'reason' => 'Please upload a valid trade licence.',
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$response = Api::Products()->AddSmsReason(
['lang' => 'en', 'reason' => 'Please upload a valid trade licence.'],
['group' => 'international-sms'],
);Deleting a Refusal Reason
Deletes a reason from one language by its position in the list.
sms or international-sms. It defaults to sms, so forgetting the parameter gives you the domestic list.lang was not given.curl -X DELETE -G 'https://panel.example.com/api/v1/admin/products/sms/reasons/0' \
-H "Authorization: Bearer $API_KEY" \
-d lang=en \
-d group=international-smsconst url = new URL('https://panel.example.com/api/v1/admin/products/sms/reasons/0');
url.searchParams.set('lang', 'en');
url.searchParams.set('group', 'international-sms');
const res = await fetch(url, {
method: 'DELETE',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$url = 'https://panel.example.com/api/v1/admin/products/sms/reasons/0?' . http_build_query([
'lang' => 'en',
'group' => 'international-sms',
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'DELETE',
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Positions SHIFT after a delete: going back to front is the safe order for several.
foreach ([2, 1] as $index)
Api::Products()->DeleteSmsReason(['index' => $index], ['lang' => 'en']);Reading the Settings
Returns the module, pricing and pre-registration settings for international SMS.
none when none is chosen.curl 'https://panel.example.com/api/v1/admin/products/sms/intl-settings' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/sms/intl-settings', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/sms/intl-settings');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$response = Api::Products()->GetSmsIntlSettings();Writing the Settings
Sets the module, the pricing job and the pre-registration countries, or changes only the margin.
curl -X PUT 'https://panel.example.com/api/v1/admin/products/sms/intl-settings' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"active_module":"Twilio","cron_status":true,"primary_currency":1}'const res = await fetch('https://panel.example.com/api/v1/admin/products/sms/intl-settings', {
method: 'PUT',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
active_module: 'Twilio',
cron_status: true,
primary_currency: 1,
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/sms/intl-settings');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'active_module' => 'Twilio',
'cron_status' => true,
'primary_currency' => 1,
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// With 'margin' in the body the OTHER fields are not read - make them two requests.
Api::Products()->SetSmsIntlSettings(['active_module' => 'Twilio']);
Api::Products()->SetSmsIntlSettings(['margin' => 20]);Reading the Prices
Returns the cost and the selling price for each country.
curl 'https://panel.example.com/api/v1/admin/products/sms/intl-pricing' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/sms/intl-pricing', {
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/sms/intl-pricing');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);$response = Api::Products()->GetSmsIntlPricing();Writing the Prices
Writes the cost and the price per country by hand.
values was empty.curl -X PUT 'https://panel.example.com/api/v1/admin/products/sms/intl-pricing' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"values":{"US":{"cost":0.01,"amount":0.02,"cid":1,"status":true}}}'const res = await fetch('https://panel.example.com/api/v1/admin/products/sms/intl-pricing', {
method: 'PUT',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
values: {
US: { cost: 0.01, amount: 0.02, cid: 1, status: true },
},
}),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/sms/intl-pricing');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'values' => [
'US' => ['cost' => 0.01, 'amount' => 0.02, 'cid' => 1, 'status' => true],
],
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The country code goes in UPPER case; do not mix it with the lower-case code on applications.
Api::Products()->SetSmsIntlPricing([
'values' => [
'US' => ['cost' => 0.01, 'amount' => 0.02, 'cid' => 1, 'status' => true],
],
]);Pulling Prices from the Module
Pulls the country prices from the provider and writes them with the margin applied.
curl -X POST 'https://panel.example.com/api/v1/admin/products/sms/intl-pricing/auto' \
-H "Authorization: Bearer $API_KEY"const res = await fetch('https://panel.example.com/api/v1/admin/products/sms/intl-pricing/auto', {
method: 'POST',
headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/sms/intl-pricing/auto');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . $apiKey],
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// The endpoint has three preconditions: a module is chosen, it can fetch prices, a currency is set.
$settings = Api::Products()->GetSmsIntlSettings()['data'];
if ($settings['active_module'] !== 'none' && $settings['primary_currency'] > 0)
Api::Products()->AutoDefineSmsIntlPricing();Clearing the Reports
Deletes the SMS records sent on and before the date you give.
domestic or international. Defaults to domestic.curl -X POST 'https://panel.example.com/api/v1/admin/products/sms/clear-reports' \
-H "Authorization: Bearer $API_KEY" \
-H 'Content-Type: application/json' \
-d '{"date":"2026-01-01","type":"international"}'const res = await fetch('https://panel.example.com/api/v1/admin/products/sms/clear-reports', {
method: 'POST',
headers: {
Authorization: `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ date: '2026-01-01', type: 'international' }),
});
const body = await res.json();$ch = curl_init('https://panel.example.com/api/v1/admin/products/sms/clear-reports');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . $apiKey,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'date' => '2026-01-01',
'type' => 'international',
]),
]);
$body = json_decode(curl_exec($ch), true);
curl_close($ch);// Leave the channel out and the DOMESTIC records go; ask for international explicitly.
Api::Products()->ClearSmsReports([
'date' => '2026-01-01',
'type' => 'international',
]);Pitfalls
The application records return the country code in lower case while the pricing endpoints expect and return upper case. Code that matches the two without normalising the case finds no country at all, and does so quietly.
With margin in the body only the margin branch runs; the module, the currency and the country list are not read. To change both, send two separate requests.
Pulling prices from the module writes over the country prices you set by hand. If you priced a few countries specially you have to enter them again after this call, and with the pricing job on the same overwrite happens on a schedule.
The bulk endpoint only changes the status; there is no field for a refusal message. If the client is meant to see why they were refused, the applications have to be settled one at a time.
Reasons are deleted by their position in the list, not by an id. Deleting one shifts everything after it down by one, so when deleting several go back to front or you will remove the wrong text.
Related Articles
Thanks for your feedback!
Our support team is here around the clock for anything you can't find above.