Документация API
Программный доступ к виртуальным номерам, заказам и балансу аккаунта.
Обзор
Все денежные поля в API /v1 указаны в IDR (индонезийская рупия), целыми числами — например, "price": 15000 и "balance": 500000 означают 15 000 ₨ и 500 000 ₨. Чтобы получить USD-проекцию того же реестра, переключитесь на API v2 с помощью переключателя версий выше.
⟩Аутентификация
Все API-запросы требуют Bearer token. Сгенерируйте его в разделе Настройки аккаунта в личном кабинете и включайте в каждый запрос:
Запросы без валидного токена получат ответ 401 UNAUTHORIZED.
⟩Базовый URL
Все пути endpoint ниже указаны относительно:
⟩Формат ответов
Каждый ответ возвращает JSON в едином формате. Все ответы содержат заголовок x-request-id для отладки.
{
"success": true,
"data": { ... }
} {
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message"
}
} Все денежные поля в API /v1 указаны в IDR (индонезийская рупия), целыми числами — например, "price": 15000 и "balance": 500000 означают 15 000 ₨ и 500 000 ₨. Чтобы получить USD-проекцию того же реестра, переключитесь на API v2 с помощью переключателя версий выше.
/catalog/countries Возвращает список всех доступных стран.
Параметры
Нет
Пример запроса
curl -s https://api.smscode.gg/v1/catalog/countries \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/catalog/countries", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/catalog/countries",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": [
{
"id": 6,
"code": "ID",
"name": "Indonesia",
"dial_code": "+62",
"emoji": "🇮🇩",
"active": true
}
]
} /catalog/services Возвращает список доступных сервисов (платформ). Опционально можно фильтровать по стране.
Query-параметры
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
country_id | integer | Нет | Фильтрация сервисов для указанной страны |
Пример запроса
curl -s "https://api.smscode.gg/v1/catalog/services?country_id=6" \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/catalog/services?country_id=6", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/catalog/services",
params={"country_id": 6},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": [
{
"id": 3,
"code": "wa",
"name": "WhatsApp",
"active": true
}
]
} /catalog/products Возвращает постраничный список доступных продуктов. Фильтрация по стране и/или платформе.
Query-параметры
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
country_id | integer | Нет | Фильтр по ID страны |
platform_id | integer | Нет | Фильтр по ID платформы/сервиса |
sort | string | Нет | Порядок сортировки: price_asc (по умолчанию), price_desc, available_asc, available_desc, name_asc, name_desc |
limit | integer | Нет | Результатов на странице (1-10 000, по умолчанию 1 000) |
page | integer | Нет | Номер страницы (мин. 1, по умолчанию 1) |
Пример запроса
curl -s "https://api.smscode.gg/v1/catalog/products?country_id=6&platform_id=3&limit=10&page=1" \
-H "Authorization: Bearer YOUR_API_TOKEN"const params = new URLSearchParams({
country_id: "6", platform_id: "3", limit: "10", page: "1",
});
const res = await fetch(`https://api.smscode.gg/v1/catalog/products?${params}`, {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/catalog/products",
params={"country_id": 6, "platform_id": 3, "limit": 10, "page": 1},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": [
{
"id": 142,
"name": "WhatsApp Indonesia",
"country_id": 6,
"platform_id": 3,
"catalog_product_id": 87,
"available": 42,
"price": 15000,
"active": true
}
],
"meta": { "page": 1, "limit": 10, "count": 1 }
} /catalog/exchange-rate Возвращает текущий обменный курс USD/IDR, используемый для конвертации валют.
Query-параметры
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
pair | string | Нет | Валютная пара (по умолчанию: USD/IDR) |
Пример запроса
curl -s "https://api.smscode.gg/v1/catalog/exchange-rate?pair=USD/IDR" \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/catalog/exchange-rate?pair=USD/IDR", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/catalog/exchange-rate",
params={"pair": "USD/IDR"},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"pair": "USD/IDR",
"base_currency": "USD",
"quote_currency": "IDR",
"rate": 16250
}
} /balance Возвращает баланс аутентифицированного пользователя.
Параметры
Нет
Пример запроса
curl -s https://api.smscode.gg/v1/balance \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/balance", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/balance",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"currency": "IDR",
"balance": 500000
}
} /orders Возвращает список заказов аутентифицированного пользователя, отсортированный по дате (новые первыми). Поддерживает фильтрацию по статусу и пагинацию через offset.
Query-параметры
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
limit | integer | Нет | Макс. результатов (1-100, по умолчанию 20) |
offset | integer | Нет | Количество пропускаемых результатов (по умолчанию 0) |
status | string | Нет | Фильтр по статусу: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (регистронезависимый) |
Пример запроса
curl -s "https://api.smscode.gg/v1/orders?limit=5&status=ACTIVE" \
-H "Authorization: Bearer YOUR_API_TOKEN"const params = new URLSearchParams({
limit: "5", status: "ACTIVE", offset: "0",
});
const res = await fetch(`https://api.smscode.gg/v1/orders?${params}`, {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/orders",
params={"limit": 5, "status": "ACTIVE", "offset": 0},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": [
{
"id": 1001,
"status": "ACTIVE",
"created_at": "2026-02-25T10:00:00+00:00",
"product_id": 142,
"phone_number": "+6281234567890",
"amount": 15000,
"otp_code": null,
"otp_received_at": null,
"expires_at": "2026-02-25T10:20:00+00:00",
"canceled_at": null,
"failed_reason": null
}
]
} /orders/{id} Возвращает один заказ по ID. Доступны только заказы аутентифицированного пользователя.
Path-параметры
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
id | integer | Да | ID заказа (path-параметр) |
Пример запроса
curl -s https://api.smscode.gg/v1/orders/1001 \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/orders/1001", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/orders/1001",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"id": 1001,
"status": "OTP_RECEIVED",
"created_at": "2026-02-25T10:00:00+00:00",
"product_id": 142,
"phone_number": "+6281234567890",
"amount": 15000,
"otp_code": "123456",
"otp_received_at": "2026-02-25T10:05:00+00:00",
"expires_at": "2026-02-25T10:20:00+00:00",
"canceled_at": null,
"failed_reason": null
}
} /orders/active Список всех активных заказов (ACTIVE + OTP_RECEIVED). Используйте для отслеживания статуса OTP.
Параметры
Нет
Пример запроса
curl -s https://api.smscode.gg/v1/orders/active \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/orders/active", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/orders/active",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": [
{
"id": 1001,
"status": "OTP_RECEIVED",
"otp_code": "123456",
"otp_message": "Your verification code is 123456",
"otp_received_at": "2026-02-25T10:05:00+00:00",
"expires_at": "2026-02-25T10:20:00+00:00",
"failed_reason": null
},
{
"id": 1002,
"status": "ACTIVE",
"otp_code": null,
"otp_message": null,
"otp_received_at": null,
"expires_at": "2026-02-25T10:50:00+00:00",
"failed_reason": null
}
]
} /orders/create Создаёт новый заказ виртуального номера. Баланс списывается автоматически. Поддерживает опциональный заголовок Idempotency-Key для предотвращения дублирования заказов при повторных запросах.
Тело запроса
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
product_id | integer | Нет | ID продукта для заказа. Укажите ТОЛЬКО ОДНО: product_id ИЛИ catalog_product_id, но не оба. |
catalog_product_id | integer | Нет | Стабильный ID продукта — сервер сам подберёт лучшее текущее предложение (самое дешёвое, с учётом надёжности). Укажите либо это, либо product_id. |
max_price | integer · string | Нет | Необязательный лимит цены. v1: целое число в IDR. v2: строка с десятичным USD (напр. "0.50"). |
prefer_provider | string | Нет | Необязательный код провайдера, предпочтительный при равных предложениях. |
policy | string | Нет | Необязательная политика маршрутизации, действует только с catalog_product_id. Значения: cheapest (по умолчанию) выбирает самое дешёвое исправное предложение; best_success сначала ранжирует предложения по недавней успешности доставки. best_success оценивает каждого провайдера по доле заказов, получивших OTP, за последние 30 завершённых дней, по полосам в 10%, и учитывает провайдера только при наличии не менее 20 заказов за этот период — провайдеры ниже этого порога или без истории считаются нейтральными, поэтому новые предложения никогда не остаются без шанса (по запросу; сигнал стартует с нейтрального значения). Если также задан prefer_provider, предпочитаемый провайдер всё равно идёт первым. |
quantity | integer | Нет | Количество (1-100, по умолчанию 1) |
Передайте заголовок Idempotency-Key для безопасного повторения запросов без создания дубликатов. Ключ может содержать буквы, цифры, дефис и подчёркивание (A-Z a-z 0-9 _ -), не более 128 символов; недопустимый ключ отклоняется с 422 VALIDATION_ERROR. Повтор с тем же ключом и тем же телом запроса возвращает исходный результат (включая failed_count при частичном успехе). Повтор, дошедший до провайдера, но завершившийся ошибкой, записывается и при повторе возвращает ту же ошибку — используйте НОВЫЙ ключ для новой попытки. Сбои без побочных эффектов (недостаточно средств, нет доступного предложения) освобождают ключ, поэтому вы можете пополнить баланс и повторить с тем же ключом. Повторное использование ключа с другим телом возвращает 422 IDEMPOTENCY_KEY_REUSED, а ещё выполняющийся запрос с этим ключом — 409 REQUEST_IN_PROGRESS. Поле failed_reason в ответах на create всегда null — оно заполняется только при опросе/в списке заказов.
Пример запроса
curl -s -X POST https://api.smscode.gg/v1/orders/create \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id-123" \
-d '{"product_id":142,"quantity":1}'
# Or order by stable catalog_product_id — the server picks the best current offer
# (cheapest, reliability-aware). Optional max_price caps the price; on v1 it is an
# IDR integer, on v2 it is a USD decimal string (e.g. "0.50"). prefer_provider
# (provider code) is a soft tie-breaker. Pass EITHER product_id OR catalog_product_id.
curl -s -X POST https://api.smscode.gg/v1/orders/create \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id-124" \
-d '{"catalog_product_id":87,"max_price":20000}'const res = await fetch("https://api.smscode.gg/v1/orders/create", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Idempotency-Key": "unique-request-id-123",
},
body: JSON.stringify({ product_id: 142, quantity: 1 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v1/orders/create",
json={"product_id": 142, "quantity": 1},
headers={
"Authorization": "Bearer YOUR_API_TOKEN",
"Idempotency-Key": "unique-request-id-123",
})
data = res.json()Пример ответа
{
"success": true,
"data": {
"orders": [
{
"id": 1002,
"status": "ACTIVE",
"product_id": 142,
"catalog_product_id": 87,
"amount": 15000,
"phone_number": "+6281234567891",
"otp_code": null,
"otp_received_at": null,
"expires_at": "2026-02-25T10:50:00+00:00",
"failed_reason": null
}
],
"failed_count": 0
}
} /orders/cancel Отмена активного заказа. Стоимость аренды возвращается на баланс аккаунта.
Тело запроса
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
id | integer | Да | ID заказа для отмены |
Пример запроса
curl -s -X POST https://api.smscode.gg/v1/orders/cancel \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":1001}'const res = await fetch("https://api.smscode.gg/v1/orders/cancel", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v1/orders/cancel",
json={"id": 1001},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"order_id": 1001,
"status": "CANCELED",
"refund_amount": 15000,
"new_balance": 515000
}
} /orders/finish Отметить заказ как завершённый после получения OTP. Номер освобождается сразу, без ожидания истечения срока.
Тело запроса
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
id | integer | Да | ID заказа для завершения |
Пример запроса
curl -s -X POST https://api.smscode.gg/v1/orders/finish \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":1001}'const res = await fetch("https://api.smscode.gg/v1/orders/finish", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v1/orders/finish",
json={"id": 1001},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"order_id": 1001,
"status": "COMPLETED"
}
} /orders/resend Запросить повторную отправку SMS на арендованный номер. Не все платформы поддерживают повторную отправку — проверяйте поле resent в ответе.
Тело запроса
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
id | integer | Да | ID заказа для повторной отправки SMS |
Пример запроса
curl -s -X POST https://api.smscode.gg/v1/orders/resend \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":1001}'const res = await fetch("https://api.smscode.gg/v1/orders/resend", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v1/orders/resend",
json={"id": 1001},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"order_id": 1001,
"status": "ACTIVE",
"resent": true
}
} /webhook Возвращает текущую конфигурацию webhook-уведомлений.
Параметры
Нет
Пример запроса
curl -s https://api.smscode.gg/v1/webhook \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/webhook", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v1/webhook",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} /webhook Обновить URL и/или секрет webhook. Секрет генерируется автоматически при первой установке URL. Отправьте пустую строку для очистки. URL должен использовать HTTPS.
Тело запроса
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
webhook_url | string | Нет | HTTPS URL для получения webhook-событий (пустая строка для очистки) |
webhook_secret | string | Нет | Общий секрет для подписи HMAC-SHA256 (генерируется автоматически, если не указан при первой настройке) |
Необходимо указать хотя бы одно поле.
Пример запроса
curl -s -X PATCH https://api.smscode.gg/v1/webhook \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"webhook_url":"https://example.com/webhook"}'const res = await fetch("https://api.smscode.gg/v1/webhook", {
method: "PATCH",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ webhook_url: "https://example.com/webhook" }),
});
const data = await res.json();import requests
res = requests.patch("https://api.smscode.gg/v1/webhook",
json={"webhook_url": "https://example.com/webhook"},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} /webhook/test Отправить тестовое событие на настроенный webhook URL. Возвращает HTTP-код ответа вашего сервера. Полезно для проверки работоспособности endpoint перед боевым использованием.
Параметры
Нет
Пример запроса
curl -s -X POST https://api.smscode.gg/v1/webhook/test \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v1/webhook/test", {
method: "POST",
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v1/webhook/test",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"status_code": 200
}
} ⟩Webhook-уведомления
Настройте webhook URL для получения push-уведомлений в реальном времени о событиях заказов вместо периодического опроса. Рекомендуемый подход для бот-скриптов.
События
| Событие | Триггер |
|---|---|
order.otp_received | OTP-код доставлен на арендованный номер |
order.completed | Заказ отмечен как завершённый (вручную или по истечении срока) |
order.expired | Заказ истёк без OTP (баланс возвращён) |
order.canceled | Заказ отменён пользователем (баланс возвращён) |
Тело запроса
{
"event": "order.otp_received",
"timestamp": "2026-02-25T12:00:00+00:00",
"data": {
"order_id": 1001,
"phone_number": "+628123456789",
"otp_code": "1234",
"otp_message": "Your verification code is 1234",
"product_id": 42,
"catalog_product_id": 87,
"country": "Indonesia",
"platform": "WhatsApp"
}
} Проверка подписи
Каждый webhook-запрос содержит заголовок X-Webhook-Signature с HMAC-SHA256 подписью тела запроса, используя ваш webhook_secret в качестве ключа:
Проверяйте эту подпись на своём сервере для подтверждения подлинности запроса. Доставка — однократная, с таймаутом 3 секунды, без повторов.
⟩Rate Limits
API-запросы ограничены по частоте для каждой группы endpoint. Превышение лимита возвращает 429 Too Many Requests с заголовком Retry-After, указывающим время ожидания в секундах.
| Группа endpoint | Лимит | Окно |
|---|---|---|
| Каталог (countries, services, products, exchange-rate) | 5 000 запросов | 60 секунд |
| Баланс | 600 запросов | 60 секунд |
| Чтение заказов (list, get, active) | 5 000 запросов | 60 секунд |
| Создание заказа | 3 000 запросов | 60 секунд |
| Отмена заказа | 1 000 запросов | 60 секунд |
| Действия с заказом (finish, resend) | 1 000 запросов | 60 секунд |
| Настройка webhook (get, update) | 600 запросов | 60 секунд |
| Тест webhook | 10 запросов | 60 секунд |
⟩Коды ошибок
Ответы с ошибкой содержат один из следующих кодов в error.code:
| Код | HTTP | Описание |
|---|---|---|
UNAUTHORIZED | 401 | Отсутствует или недействителен API-токен |
FORBIDDEN | 403 | Доступ запрещён |
NOT_FOUND | 404 | Ресурс не найден (заказ, обменный курс и т.д.) |
CONFLICT | 409 | Дублирующий запрос или конфликт ресурсов |
INSUFFICIENT_BALANCE | 409 | Недостаточно средств на балансе |
VALIDATION_ERROR | 422 | Параметры запроса не прошли валидацию |
RATE_LIMIT_EXCEEDED | 429 | Слишком много запросов (проверьте заголовок Retry-After) |
INTERNAL_ERROR | 500 | Внутренняя ошибка сервера |
PROVIDER_ERROR | 422 | Вышестоящий SMS-провайдер отклонил запрос. При сбоях создания заказа ошибка может содержать <code>details</code>: <code>cause_counts</code> (заказы со старым <code>product_id</code> — сводка с группировкой по причине) или <code>attempts</code> (заказы с <code>catalog_product_id</code> — результаты по каждой попытке), используя значения <code>ok</code>, <code>no_numbers</code>, <code>insufficient_balance</code>, <code>price_rejected</code>, <code>provider_unavailable</code>, <code>provider_account_balance</code>, <code>provider_error</code>. |
NO_OFFER_AVAILABLE | 422 | Нет активного предложения, подходящего под запрошенный продукт и правила (лимит цены, наличие). |
CANCEL_TOO_EARLY | 409 | Заказ слишком новый для отмены — подождите 2 минуты |
REQUEST_IN_PROGRESS | 409 | Запрос на создание с этим ключом идемпотентности ещё выполняется |
IDEMPOTENCY_KEY_REUSED | 422 | Этот ключ идемпотентности уже использовался с другим телом запроса |
SERVICE_UNAVAILABLE | 503 | Сервис временно недоступен (обслуживание) |
Обзор
Все денежные поля в API /v2 указаны в USD и возвращаются как денежный объект — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }. amount — это десятичная строка; canonical_amount — точное значение реестра в IDR (используйте его для сверки). Применённый курс USD/IDR rate раскрывается один раз на ответ в meta.fx. v2 — это USD-проекция во время рендеринга поверх того же реестра в IDR, что и v1, — она никогда не хранит и не проводит операции в USD.
⟩Аутентификация
Все API-запросы требуют Bearer token. Сгенерируйте его в разделе Настройки аккаунта в личном кабинете и включайте в каждый запрос:
Запросы без валидного токена получат ответ 401 UNAUTHORIZED.
⟩Базовый URL
Все пути endpoint ниже указаны относительно:
⟩Формат ответов
Каждый ответ возвращает JSON в едином формате. Все ответы содержат заголовок x-request-id для отладки.
{
"success": true,
"data": { ... }
} {
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message"
}
} Ошибка v2: нет пригодного курса (503)
{
"success": false,
"error": { "code": "FX_RATE_UNAVAILABLE", "message": "USD/IDR exchange rate is unavailable" }
} Все денежные поля в API /v2 указаны в USD и возвращаются как денежный объект — { "amount": "0.92", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" }. amount — это десятичная строка; canonical_amount — точное значение реестра в IDR (используйте его для сверки). Применённый курс USD/IDR rate раскрывается один раз на ответ в meta.fx. v2 — это USD-проекция во время рендеринга поверх того же реестра в IDR, что и v1, — она никогда не хранит и не проводит операции в USD.
/catalog/countries Возвращает список всех доступных стран.
Параметры
Нет
Пример запроса
curl -s https://api.smscode.gg/v2/catalog/countries \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/catalog/countries", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/catalog/countries",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": [
{
"id": 6,
"code": "ID",
"name": "Indonesia",
"dial_code": "+62",
"emoji": "🇮🇩",
"active": true
}
]
} Идентично v1 — меняется только базовый путь (/v1 → /v2).
/catalog/services Возвращает список доступных сервисов (платформ). Опционально можно фильтровать по стране.
Query-параметры
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
country_id | integer | Нет | Фильтрация сервисов для указанной страны |
Пример запроса
curl -s "https://api.smscode.gg/v2/catalog/services?country_id=6" \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/catalog/services?country_id=6", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/catalog/services",
params={"country_id": 6},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": [
{
"id": 3,
"code": "wa",
"name": "WhatsApp",
"active": true
}
]
} Идентично v1 — меняется только базовый путь (/v1 → /v2).
/catalog/products Возвращает постраничный список доступных продуктов. Фильтрация по стране и/или платформе.
Query-параметры
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
country_id | integer | Нет | Фильтр по ID страны |
platform_id | integer | Нет | Фильтр по ID платформы/сервиса |
sort | string | Нет | Порядок сортировки: price_asc (по умолчанию), price_desc, available_asc, available_desc, name_asc, name_desc |
limit | integer | Нет | Результатов на странице (1-10 000, по умолчанию 1 000) |
page | integer | Нет | Номер страницы (мин. 1, по умолчанию 1) |
Пример запроса
curl -s "https://api.smscode.gg/v2/catalog/products?country_id=6&platform_id=3&limit=10&page=1" \
-H "Authorization: Bearer YOUR_API_TOKEN"const params = new URLSearchParams({
country_id: "6", platform_id: "3", limit: "10", page: "1",
});
const res = await fetch(`https://api.smscode.gg/v2/catalog/products?${params}`, {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/catalog/products",
params={"country_id": 6, "platform_id": 3, "limit": 10, "page": 1},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": [
{
"id": 142,
"name": "WhatsApp Indonesia",
"country_id": 6,
"platform_id": 3,
"catalog_product_id": 87,
"available": 42,
"price": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
"active": true
}
],
"meta": { "page": 1, "limit": 10, "count": 1, "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
} v2: денежные поля представлены денежными объектами в USD, а ответ содержит единый meta.fx { pair, rate, rate_as_of }. rate — это целое число IDR за 1 USD, поэтому USD = canonical_amount / rate. Итоги используют 2 знака после запятой; цены и возвраты по позициям — 4. Строго положительная сумма никогда не округляется до 0.00. rate_as_of — это метка времени курса в формате RFC3339 (вид +00:00) либо null, если метка времени не зафиксирована.
Только v2: если пригодного курса USD/IDR нет, денежные endpoint'ы возвращают 503 FX_RATE_UNAVAILABLE с заголовком Retry-After вместо денежного тела ответа. v1 такого никогда не возвращает.
/catalog/exchange-rate Возвращает текущий обменный курс USD/IDR, используемый для конвертации валют.
Параметры
Нет — v2 всегда возвращает USD/IDR; параметр ?pair из v1 игнорируется.
Пример запроса
curl -s "https://api.smscode.gg/v2/catalog/exchange-rate?pair=USD/IDR" \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/catalog/exchange-rate?pair=USD/IDR", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/catalog/exchange-rate",
params={"pair": "USD/IDR"},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" }
} v2: возвращает { pair, rate, rate_as_of } (без base_currency/quote_currency, без обёртки meta — курс и есть данные). ?pair игнорируется — v2 всегда возвращает USD/IDR (v1 учитывает ?pair). Возвращает 503 FX_RATE_UNAVAILABLE, если пригодного курса нет.
/balance Возвращает баланс аутентифицированного пользователя.
Параметры
Нет
Пример запроса
curl -s https://api.smscode.gg/v2/balance \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/balance", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/balance",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"balance": { "amount": "30.77", "currency": "USD", "canonical_amount": 500000, "canonical_currency": "IDR" }
},
"meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
} v2: денежные поля представлены денежными объектами в USD, а ответ содержит единый meta.fx { pair, rate, rate_as_of }. rate — это целое число IDR за 1 USD, поэтому USD = canonical_amount / rate. Итоги используют 2 знака после запятой; цены и возвраты по позициям — 4. Строго положительная сумма никогда не округляется до 0.00. rate_as_of — это метка времени курса в формате RFC3339 (вид +00:00) либо null, если метка времени не зафиксирована.
Только v2: если пригодного курса USD/IDR нет, денежные endpoint'ы возвращают 503 FX_RATE_UNAVAILABLE с заголовком Retry-After вместо денежного тела ответа. v1 такого никогда не возвращает.
/orders Возвращает список заказов аутентифицированного пользователя, отсортированный по дате (новые первыми). Поддерживает фильтрацию по статусу и пагинацию через offset.
Query-параметры
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
limit | integer | Нет | Макс. результатов (1-100, по умолчанию 20) |
offset | integer | Нет | Количество пропускаемых результатов (по умолчанию 0) |
status | string | Нет | Фильтр по статусу: ACTIVE, OTP_RECEIVED, COMPLETED, CANCELED, EXPIRED (регистронезависимый) |
Пример запроса
curl -s "https://api.smscode.gg/v2/orders?limit=5&status=ACTIVE" \
-H "Authorization: Bearer YOUR_API_TOKEN"const params = new URLSearchParams({
limit: "5", status: "ACTIVE", offset: "0",
});
const res = await fetch(`https://api.smscode.gg/v2/orders?${params}`, {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/orders",
params={"limit": 5, "status": "ACTIVE", "offset": 0},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": [
{
"id": 1001,
"status": "ACTIVE",
"created_at": "2026-02-25T10:00:00+00:00",
"product_id": 142,
"phone_number": "+6281234567890",
"amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
"otp_code": null,
"otp_received_at": null,
"expires_at": "2026-02-25T10:20:00+00:00",
"canceled_at": null,
"failed_reason": null
}
],
"meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
} v2: денежные поля представлены денежными объектами в USD, а ответ содержит единый meta.fx { pair, rate, rate_as_of }. rate — это целое число IDR за 1 USD, поэтому USD = canonical_amount / rate. Итоги используют 2 знака после запятой; цены и возвраты по позициям — 4. Строго положительная сумма никогда не округляется до 0.00. rate_as_of — это метка времени курса в формате RFC3339 (вид +00:00) либо null, если метка времени не зафиксирована.
Только v2: если пригодного курса USD/IDR нет, денежные endpoint'ы возвращают 503 FX_RATE_UNAVAILABLE с заголовком Retry-After вместо денежного тела ответа. v1 такого никогда не возвращает.
/orders/{id} Возвращает один заказ по ID. Доступны только заказы аутентифицированного пользователя.
Path-параметры
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
id | integer | Да | ID заказа (path-параметр) |
Пример запроса
curl -s https://api.smscode.gg/v2/orders/1001 \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/orders/1001", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/orders/1001",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"id": 1001,
"status": "OTP_RECEIVED",
"created_at": "2026-02-25T10:00:00+00:00",
"product_id": 142,
"phone_number": "+6281234567890",
"amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
"otp_code": "123456",
"otp_received_at": "2026-02-25T10:05:00+00:00",
"expires_at": "2026-02-25T10:20:00+00:00",
"canceled_at": null,
"failed_reason": null
},
"meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
} v2: денежные поля представлены денежными объектами в USD, а ответ содержит единый meta.fx { pair, rate, rate_as_of }. rate — это целое число IDR за 1 USD, поэтому USD = canonical_amount / rate. Итоги используют 2 знака после запятой; цены и возвраты по позициям — 4. Строго положительная сумма никогда не округляется до 0.00. rate_as_of — это метка времени курса в формате RFC3339 (вид +00:00) либо null, если метка времени не зафиксирована.
Только v2: если пригодного курса USD/IDR нет, денежные endpoint'ы возвращают 503 FX_RATE_UNAVAILABLE с заголовком Retry-After вместо денежного тела ответа. v1 такого никогда не возвращает.
/orders/active Список всех активных заказов (ACTIVE + OTP_RECEIVED). Используйте для отслеживания статуса OTP.
Параметры
Нет
Пример запроса
curl -s https://api.smscode.gg/v2/orders/active \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/orders/active", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/orders/active",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": [
{
"id": 1001,
"status": "OTP_RECEIVED",
"otp_code": "123456",
"otp_message": "Your verification code is 123456",
"otp_received_at": "2026-02-25T10:05:00+00:00",
"expires_at": "2026-02-25T10:20:00+00:00",
"failed_reason": null
},
{
"id": 1002,
"status": "ACTIVE",
"otp_code": null,
"otp_message": null,
"otp_received_at": null,
"expires_at": "2026-02-25T10:50:00+00:00",
"failed_reason": null
}
]
} v2: этот endpoint не является денежным — он не возвращает ни amount, ни meta.fx (та же структура, что и в v1, но в /v2).
/orders/create Создаёт новый заказ виртуального номера. Баланс списывается автоматически. Поддерживает опциональный заголовок Idempotency-Key для предотвращения дублирования заказов при повторных запросах.
Тело запроса
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
product_id | integer | Нет | ID продукта для заказа. Укажите ТОЛЬКО ОДНО: product_id ИЛИ catalog_product_id, но не оба. |
catalog_product_id | integer | Нет | Стабильный ID продукта — сервер сам подберёт лучшее текущее предложение (самое дешёвое, с учётом надёжности). Укажите либо это, либо product_id. |
max_price | integer · string | Нет | Необязательный лимит цены. v1: целое число в IDR. v2: строка с десятичным USD (напр. "0.50"). |
prefer_provider | string | Нет | Необязательный код провайдера, предпочтительный при равных предложениях. |
policy | string | Нет | Необязательная политика маршрутизации, действует только с catalog_product_id. Значения: cheapest (по умолчанию) выбирает самое дешёвое исправное предложение; best_success сначала ранжирует предложения по недавней успешности доставки. best_success оценивает каждого провайдера по доле заказов, получивших OTP, за последние 30 завершённых дней, по полосам в 10%, и учитывает провайдера только при наличии не менее 20 заказов за этот период — провайдеры ниже этого порога или без истории считаются нейтральными, поэтому новые предложения никогда не остаются без шанса (по запросу; сигнал стартует с нейтрального значения). Если также задан prefer_provider, предпочитаемый провайдер всё равно идёт первым. |
quantity | integer | Нет | Количество (1-100, по умолчанию 1) |
Передайте заголовок Idempotency-Key для безопасного повторения запросов без создания дубликатов. Ключ может содержать буквы, цифры, дефис и подчёркивание (A-Z a-z 0-9 _ -), не более 128 символов; недопустимый ключ отклоняется с 422 VALIDATION_ERROR. Повтор с тем же ключом и тем же телом запроса возвращает исходный результат (включая failed_count при частичном успехе). Повтор, дошедший до провайдера, но завершившийся ошибкой, записывается и при повторе возвращает ту же ошибку — используйте НОВЫЙ ключ для новой попытки. Сбои без побочных эффектов (недостаточно средств, нет доступного предложения) освобождают ключ, поэтому вы можете пополнить баланс и повторить с тем же ключом. Повторное использование ключа с другим телом возвращает 422 IDEMPOTENCY_KEY_REUSED, а ещё выполняющийся запрос с этим ключом — 409 REQUEST_IN_PROGRESS. Поле failed_reason в ответах на create всегда null — оно заполняется только при опросе/в списке заказов.
Пример запроса
curl -s -X POST https://api.smscode.gg/v2/orders/create \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id-123" \
-d '{"product_id":142,"quantity":1}'
# Or order by stable catalog_product_id — the server picks the best current offer
# (cheapest, reliability-aware). Optional max_price caps the price; on v1 it is an
# IDR integer, on v2 it is a USD decimal string (e.g. "0.50"). prefer_provider
# (provider code) is a soft tie-breaker. Pass EITHER product_id OR catalog_product_id.
curl -s -X POST https://api.smscode.gg/v2/orders/create \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: unique-request-id-124" \
-d '{"catalog_product_id":87,"max_price":20000}'const res = await fetch("https://api.smscode.gg/v2/orders/create", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
"Idempotency-Key": "unique-request-id-123",
},
body: JSON.stringify({ product_id: 142, quantity: 1 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v2/orders/create",
json={"product_id": 142, "quantity": 1},
headers={
"Authorization": "Bearer YOUR_API_TOKEN",
"Idempotency-Key": "unique-request-id-123",
})
data = res.json()Пример ответа
{
"success": true,
"data": {
"orders": [
{
"id": 1002,
"status": "ACTIVE",
"product_id": 142,
"catalog_product_id": 87,
"amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
"phone_number": "+6281234567891",
"otp_code": null,
"otp_received_at": null,
"expires_at": "2026-02-25T10:50:00+00:00",
"failed_reason": null
}
],
"failed_count": 0
},
"meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
} v2: денежные поля представлены денежными объектами в USD, а ответ содержит единый meta.fx { pair, rate, rate_as_of }. rate — это целое число IDR за 1 USD, поэтому USD = canonical_amount / rate. Итоги используют 2 знака после запятой; цены и возвраты по позициям — 4. Строго положительная сумма никогда не округляется до 0.00. rate_as_of — это метка времени курса в формате RFC3339 (вид +00:00) либо null, если метка времени не зафиксирована.
Только v2: если пригодного курса USD/IDR нет, денежные endpoint'ы возвращают 503 FX_RATE_UNAVAILABLE с заголовком Retry-After вместо денежного тела ответа. v1 такого никогда не возвращает.
/orders/cancel Отмена активного заказа. Стоимость аренды возвращается на баланс аккаунта.
Тело запроса
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
id | integer | Да | ID заказа для отмены |
Пример запроса
curl -s -X POST https://api.smscode.gg/v2/orders/cancel \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":1001}'const res = await fetch("https://api.smscode.gg/v2/orders/cancel", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v2/orders/cancel",
json={"id": 1001},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"order_id": 1001,
"status": "CANCELED",
"refund_amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
"new_balance": { "amount": "31.69", "currency": "USD", "canonical_amount": 515000, "canonical_currency": "IDR" }
},
"meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
} v2: денежные поля представлены денежными объектами в USD, а ответ содержит единый meta.fx { pair, rate, rate_as_of }. rate — это целое число IDR за 1 USD, поэтому USD = canonical_amount / rate. Итоги используют 2 знака после запятой; цены и возвраты по позициям — 4. Строго положительная сумма никогда не округляется до 0.00. rate_as_of — это метка времени курса в формате RFC3339 (вид +00:00) либо null, если метка времени не зафиксирована.
Только v2: если пригодного курса USD/IDR нет, денежные endpoint'ы возвращают 503 FX_RATE_UNAVAILABLE с заголовком Retry-After вместо денежного тела ответа. v1 такого никогда не возвращает.
/orders/finish Отметить заказ как завершённый после получения OTP. Номер освобождается сразу, без ожидания истечения срока.
Тело запроса
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
id | integer | Да | ID заказа для завершения |
Пример запроса
curl -s -X POST https://api.smscode.gg/v2/orders/finish \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":1001}'const res = await fetch("https://api.smscode.gg/v2/orders/finish", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v2/orders/finish",
json={"id": 1001},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"order_id": 1001,
"status": "COMPLETED"
}
} Идентично v1 — меняется только базовый путь (/v1 → /v2).
/orders/resend Запросить повторную отправку SMS на арендованный номер. Не все платформы поддерживают повторную отправку — проверяйте поле resent в ответе.
Тело запроса
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
id | integer | Да | ID заказа для повторной отправки SMS |
Пример запроса
curl -s -X POST https://api.smscode.gg/v2/orders/resend \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"id":1001}'const res = await fetch("https://api.smscode.gg/v2/orders/resend", {
method: "POST",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ id: 1001 }),
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v2/orders/resend",
json={"id": 1001},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"order_id": 1001,
"status": "ACTIVE",
"resent": true
}
} Идентично v1 — меняется только базовый путь (/v1 → /v2).
/webhook Возвращает текущую конфигурацию webhook-уведомлений.
Параметры
Нет
Пример запроса
curl -s https://api.smscode.gg/v2/webhook \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/webhook", {
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.get("https://api.smscode.gg/v2/webhook",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} Идентично v1 — меняется только базовый путь (/v1 → /v2).
/webhook Обновить URL и/или секрет webhook. Секрет генерируется автоматически при первой установке URL. Отправьте пустую строку для очистки. URL должен использовать HTTPS.
Тело запроса
| Название | Тип | Обязательный | Описание |
|---|---|---|---|
webhook_url | string | Нет | HTTPS URL для получения webhook-событий (пустая строка для очистки) |
webhook_secret | string | Нет | Общий секрет для подписи HMAC-SHA256 (генерируется автоматически, если не указан при первой настройке) |
Необходимо указать хотя бы одно поле.
Пример запроса
curl -s -X PATCH https://api.smscode.gg/v2/webhook \
-H "Authorization: Bearer YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"webhook_url":"https://example.com/webhook"}'const res = await fetch("https://api.smscode.gg/v2/webhook", {
method: "PATCH",
headers: {
Authorization: "Bearer YOUR_API_TOKEN",
"Content-Type": "application/json",
},
body: JSON.stringify({ webhook_url: "https://example.com/webhook" }),
});
const data = await res.json();import requests
res = requests.patch("https://api.smscode.gg/v2/webhook",
json={"webhook_url": "https://example.com/webhook"},
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"webhook_url": "https://example.com/webhook",
"webhook_secret": "a1b2c3d4e5f6..."
}
} Идентично v1 — меняется только базовый путь (/v1 → /v2).
/webhook/test Отправить тестовое событие на настроенный webhook URL. Возвращает HTTP-код ответа вашего сервера. Полезно для проверки работоспособности endpoint перед боевым использованием.
Параметры
Нет
Пример запроса
curl -s -X POST https://api.smscode.gg/v2/webhook/test \
-H "Authorization: Bearer YOUR_API_TOKEN"const res = await fetch("https://api.smscode.gg/v2/webhook/test", {
method: "POST",
headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();import requests
res = requests.post("https://api.smscode.gg/v2/webhook/test",
headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()Пример ответа
{
"success": true,
"data": {
"status_code": 200
}
} Идентично v1 — меняется только базовый путь (/v1 → /v2).
⟩Webhook-уведомления
Настройте webhook URL для получения push-уведомлений в реальном времени о событиях заказов вместо периодического опроса. Рекомендуемый подход для бот-скриптов.
События
| Событие | Триггер |
|---|---|
order.otp_received | OTP-код доставлен на арендованный номер |
order.completed | Заказ отмечен как завершённый (вручную или по истечении срока) |
order.expired | Заказ истёк без OTP (баланс возвращён) |
order.canceled | Заказ отменён пользователем (баланс возвращён) |
Тело запроса
{
"event": "order.otp_received",
"timestamp": "2026-02-25T12:00:00+00:00",
"data": {
"order_id": 1001,
"phone_number": "+628123456789",
"otp_code": "1234",
"otp_message": "Your verification code is 1234",
"product_id": 42,
"catalog_product_id": 87,
"country": "Indonesia",
"platform": "WhatsApp"
}
} Проверка подписи
Каждый webhook-запрос содержит заголовок X-Webhook-Signature с HMAC-SHA256 подписью тела запроса, используя ваш webhook_secret в качестве ключа:
Проверяйте эту подпись на своём сервере для подтверждения подлинности запроса. Доставка — однократная, с таймаутом 3 секунды, без повторов.
⟩Rate Limits
API-запросы ограничены по частоте для каждой группы endpoint. Превышение лимита возвращает 429 Too Many Requests с заголовком Retry-After, указывающим время ожидания в секундах.
| Группа endpoint | Лимит | Окно |
|---|---|---|
| Каталог (countries, services, products, exchange-rate) | 5 000 запросов | 60 секунд |
| Баланс | 600 запросов | 60 секунд |
| Чтение заказов (list, get, active) | 5 000 запросов | 60 секунд |
| Создание заказа | 3 000 запросов | 60 секунд |
| Отмена заказа | 1 000 запросов | 60 секунд |
| Действия с заказом (finish, resend) | 1 000 запросов | 60 секунд |
| Настройка webhook (get, update) | 600 запросов | 60 секунд |
| Тест webhook | 10 запросов | 60 секунд |
⟩Коды ошибок
Ответы с ошибкой содержат один из следующих кодов в error.code:
| Код | HTTP | Описание |
|---|---|---|
UNAUTHORIZED | 401 | Отсутствует или недействителен API-токен |
FORBIDDEN | 403 | Доступ запрещён |
NOT_FOUND | 404 | Ресурс не найден (заказ, обменный курс и т.д.) |
CONFLICT | 409 | Дублирующий запрос или конфликт ресурсов |
INSUFFICIENT_BALANCE | 409 | Недостаточно средств на балансе |
VALIDATION_ERROR | 422 | Параметры запроса не прошли валидацию |
RATE_LIMIT_EXCEEDED | 429 | Слишком много запросов (проверьте заголовок Retry-After) |
INTERNAL_ERROR | 500 | Внутренняя ошибка сервера |
PROVIDER_ERROR | 422 | Вышестоящий SMS-провайдер отклонил запрос. При сбоях создания заказа ошибка может содержать <code>details</code>: <code>cause_counts</code> (заказы со старым <code>product_id</code> — сводка с группировкой по причине) или <code>attempts</code> (заказы с <code>catalog_product_id</code> — результаты по каждой попытке), используя значения <code>ok</code>, <code>no_numbers</code>, <code>insufficient_balance</code>, <code>price_rejected</code>, <code>provider_unavailable</code>, <code>provider_account_balance</code>, <code>provider_error</code>. |
NO_OFFER_AVAILABLE | 422 | Нет активного предложения, подходящего под запрошенный продукт и правила (лимит цены, наличие). |
CANCEL_TOO_EARLY | 409 | Заказ слишком новый для отмены — подождите 2 минуты |
REQUEST_IN_PROGRESS | 409 | Запрос на создание с этим ключом идемпотентности ещё выполняется |
IDEMPOTENCY_KEY_REUSED | 422 | Этот ключ идемпотентности уже использовался с другим телом запроса |
SERVICE_UNAVAILABLE | 503 | Сервис временно недоступен (обслуживание) |
FX_RATE_UNAVAILABLE | 503 | Обменный курс USD/IDR недоступен (денежные endpoint'ы v2) — возвращается 503 с заголовком Retry-After. |
⟩Миграция с v1 на v2
v1 работает с IDR; v2 работает с USD. Обе версии сосуществуют постоянно — отключения не планируется. Выберите одну версию на интеграцию; не смешивайте базовые пути. v2 идентична v1, отличается лишь способом представления денег.
| Аспект | v1 · IDR | v2 · USD |
|---|---|---|
| Денежные поля | Целое IDR, напр. 15000 | Денежный объект { amount, currency, canonical_amount, canonical_currency } |
meta.fx | Отсутствует | Обязателен в каждом денежном ответе |
| Валюта | IDR | USD (зашит жёстко) |
FX_RATE_UNAVAILABLE | — | Новый 503 + Retry-After, когда пригодного курса нет |
| Точность | — | Итоги 2 знака, цены/возвраты 4 знака, округление вверх для положительных |
GET /catalog/exchange-rate | {pair, base_currency, quote_currency, rate}; учитывает ?pair | {pair, rate, rate_as_of}; ?pair игнорируется (только USD/IDR) |
Примеры рядом
{
"success": true,
"data": {
"currency": "IDR",
"balance": 500000
}
}{
"success": true,
"data": {
"balance": { "amount": "30.77", "currency": "USD", "canonical_amount": 500000, "canonical_currency": "IDR" }
},
"meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}{
"success": true,
"data": [
{
"id": 142,
"name": "WhatsApp Indonesia",
"country_id": 6,
"platform_id": 3,
"catalog_product_id": 87,
"available": 42,
"price": 15000,
"active": true
}
],
"meta": { "page": 1, "limit": 10, "count": 1 }
}{
"success": true,
"data": [
{
"id": 142,
"name": "WhatsApp Indonesia",
"country_id": 6,
"platform_id": 3,
"catalog_product_id": 87,
"available": 42,
"price": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
"active": true
}
],
"meta": { "page": 1, "limit": 10, "count": 1, "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}catalog_product_id — это СТАБИЛЬНЫЙ идентификатор продукта (один на страну + платформу), его безопасно хранить в своих системах И по нему можно заказывать напрямую: укажите catalog_product_id (плюс необязательные max_price и prefer_provider), и сервер сам подберёт актуальное предложение. Путь через конкретный product_id по-прежнему поддерживается и обратно совместим — этот идентификатор изменчив и меняется при каждом сдвиге ценового тарифа провайдера, поэтому совет запрашивать каталог заново перед заказом относится только к этому пути.
{
"success": true,
"data": {
"order_id": 1001,
"status": "CANCELED",
"refund_amount": 15000,
"new_balance": 515000
}
}{
"success": true,
"data": {
"order_id": 1001,
"status": "CANCELED",
"refund_amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
"new_balance": { "amount": "31.69", "currency": "USD", "canonical_amount": 515000, "canonical_currency": "IDR" }
},
"meta": { "fx": { "pair": "USD/IDR", "rate": 16250, "rate_as_of": "2026-05-27T08:00:00+00:00" } }
}Чек-лист миграции
- Замените базовый путь
/v1→/v2. - Разбирайте денежные поля как объекты — читайте
amountкак десятичную строку;currencyравно"USD". - Для сверки реестра используйте
canonical_amount(точный IDR); сумма в USDamount— это проекция во время рендеринга, аrateраскрывается один раз вmeta.fx. - Обрабатывайте новый
FX_RATE_UNAVAILABLE(503) — повторите запрос послеRetry-After. v1 такого никогда не возвращает.