Документация API

Программный доступ к виртуальным номерам, заказам и балансу аккаунта.

Рекомендуется

Начните с официальных SDK

Для новых интеграций используйте TypeScript/JavaScript или Python SDK. Оба SDK по умолчанию работают с публичным API /v2, сохраняют ключи идемпотентности при безопасных повторах, предоставляют типизированные ошибки и поддерживают согласованный жизненный цикл OTP.

01

Создавайте безопасно

Создайте заказ с product_id для точного стабильного слота тарифа либо с catalog_product_id, необязательным operator_id, min_price/max_price и ключом идемпотентности для безопасных повторов платных маршрутизируемых вызовов.

catalog_product_idmax_priceIdempotency-Key
import { SmscodeClient, OtpTimeoutError } from "@smscode/sdk";

const client = new SmscodeClient({ token: process.env.SMSCODE_TOKEN! });

let orderId: number | undefined;

try {
  const created = await client.orders.create({
    catalog_product_id: Number(process.env.SMSCODE_CATALOG_PRODUCT_ID),
    max_price: "0.50",
    quantity: 1,
  });

  const order = created.orders[0]!;
  orderId = order.id;
  const first = await client.orders.waitForOtp(orderId, { timeoutMs: 120_000 });

  console.log(first.otpCode); // Отправьте этот код в целевом приложении.
  await client.orders.finish(orderId);
} catch (err) {
  if (err instanceof OtpTimeoutError && orderId !== undefined) {
    const current = await client.orders.get(orderId);
    if (current.can_cancel) await client.orders.cancel(orderId);
  }
  throw err;
}
import os

from smscode import OtpTimeoutError, SmscodeClient

with SmscodeClient(token=os.environ["SMSCODE_TOKEN"]) as client:
    created = client.orders.create(
        catalog_product_id=int(os.environ["SMSCODE_CATALOG_PRODUCT_ID"]),
        max_price="0.50",
        quantity=1,
    )

    order = created.orders[0]
    order_id = int(order["id"])

    try:
        first = client.orders.wait_for_otp(order_id, timeout_ms=120_000)
        print(first.otp_code)  # Отправьте этот код в целевом приложении.
        client.orders.finish(order_id)
    except OtpTimeoutError:
        current = client.orders.get(order_id)
        if current["can_cancel"]:
            client.orders.cancel(order_id)
        raise

Используйте can_resend и resend_available_at для тайминга resend. Низкоуровневые timestamps resend являются внутренними и не являются публичными полями ответа.

Обзор

Все денежные поля в API /v1 указаны в IDR (индонезийская рупия), целыми числами — например, "price": 15000 и "balance": 500000 означают 15 000 ₨ и 500 000 ₨. Чтобы получить USD-проекцию того же реестра, переключитесь на API v2 с помощью переключателя версий выше.

Аутентификация

Все API-запросы требуют Bearer token. Сгенерируйте его в разделе Настройки аккаунта в личном кабинете и включайте в каждый запрос:

Authorization:Bearer YOUR_API_TOKEN

Запросы без валидного токена получат ответ 401 UNAUTHORIZED.

Базовый URL

Все пути endpoint ниже указаны относительно:

https://api.smscode.gg/v1

Формат ответов

Каждый ответ возвращает 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 с помощью переключателя версий выше.

GET/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()

Пример ответа

200 OK
{
  "success": true,
  "data": [
    {
      "id": 6,
      "code": "ID",
      "name": "Indonesia",
      "dial_code": "+62",
      "emoji": "🇮🇩",
      "active": true
    }
  ]
}
GET/catalog/services

Возвращает список доступных сервисов (платформ). Опционально можно фильтровать по стране.

Query-параметры

НазваниеТипОбязательныйОписание
country_idintegerНетФильтрация сервисов для указанной страны

Пример запроса

curl -s "https://api.smscode.gg/v1/catalog/services?country_id=7" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/catalog/services?country_id=7", {
  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": 7},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Пример ответа

200 OK
{
  "success": true,
  "data": [
    {
      "id": 3,
      "code": "wa",
      "name": "WhatsApp",
      "active": true
    }
  ]
}
GET/catalog/operators

Возвращает операторов, доступных для выбора для страны + сервиса. Если доступны и реальные операторы, и запас Any, ответ включает строку Any с operator_id null; если продуктов для конкретных операторов нет, список пуст.

Query-параметры

НазваниеТипОбязательныйОписание
country_idintegerДаID страны
platform_idintegerДаID платформы/сервиса

Пример запроса

curl -s "https://api.smscode.gg/v1/catalog/operators?country_id=7&platform_id=3" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "7", platform_id: "3",
});
const res = await fetch(`https://api.smscode.gg/v1/catalog/operators?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/catalog/operators",
    params={"country_id": 7, "platform_id": 3},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Пример ответа

200 OK
{
  "success": true,
  "data": [
    {
      "operator_id": null,
      "code": "any",
      "name": "Any",
      "local_name": null
    },
    {
      "operator_id": 433,
      "code": "axis",
      "name": "AXIS (XL Axiata)",
      "local_name": "AXIS (XL Axiata)"
    }
  ]
}
GET/catalog/products

Возвращает постраничный список доступных продуктов. Фильтруйте по стране, платформе и при необходимости по оператору.

Query-параметры

НазваниеТипОбязательныйОписание
country_idintegerНетФильтр по ID страны
platform_idintegerНетФильтр по ID платформы/сервиса
operator_idintegerНетНеобязательный ID оператора из /catalog/operators. Не указывайте для продуктов Any.
sortstringНетПорядок сортировки: price_asc (по умолчанию), price_desc, available_asc, available_desc, name_asc, name_desc
limitintegerНетРезультатов на странице (1-10 000, по умолчанию 1 000)
pageintegerНетНомер страницы (мин. 1, по умолчанию 1)

Пример запроса

curl -s "https://api.smscode.gg/v1/catalog/products?country_id=7&platform_id=3&limit=10&page=1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "7", 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": 7, "platform_id": 3, "limit": 10, "page": 1},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Пример ответа

200 OK
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 7,
      "platform_id": 3,
      "catalog_product_id": 87,
      "operator_id": null,
      "operator_name": null,
      "available": 42,
      "price": 15000,
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1 }
}
GET/catalog/exchange-rate

Возвращает текущий обменный курс USD/IDR, используемый для конвертации валют.

Query-параметры

НазваниеТипОбязательныйОписание
pairstringНетВалютная пара (по умолчанию: 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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "pair": "USD/IDR",
    "base_currency": "USD",
    "quote_currency": "IDR",
    "rate": 16250
  }
}
GET/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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "currency": "IDR",
    "balance": 500000
  }
}
GET/orders

Возвращает список заказов аутентифицированного пользователя, отсортированный по дате (новые первыми). Поддерживает фильтрацию по статусу и пагинацию через offset.

Query-параметры

НазваниеТипОбязательныйОписание
limitintegerНетМакс. результатов (1-100, по умолчанию 20)
offsetintegerНетКоличество пропускаемых результатов (по умолчанию 0)
statusstringНетФильтр по статусу: 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()

Пример ответа

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "ACTIVE",
      "created_at": "2026-02-25T10:00:00+00:00",
      "product_id": 142,
      "catalog_product_id": 87,
      "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,
      "operator_id": null,
      "operator_name": null
    }
  ]
}
GET/orders/{id}

Возвращает один заказ по ID. Доступны только заказы аутентифицированного пользователя.

Path-параметры

НазваниеТипОбязательныйОписание
idintegerДа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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "id": 1001,
    "status": "OTP_RECEIVED",
    "created_at": "2026-02-25T10:00:00+00:00",
    "product_id": 142,
    "catalog_product_id": 87,
    "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,
    "operator_id": null,
    "operator_name": null
  }
}
GET/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()

Пример ответа

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "OTP_RECEIVED",
      "otp_code": null,
      "otp_message": "Confirm your login: https://example.com/confirm",
      "sms_revision": 1,
      "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,
      "sms_revision": 0,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:50:00+00:00",
      "failed_reason": null
    }
  ]
}
POST/orders/create

Создаёт новый заказ виртуального номера. Баланс списывается автоматически. Поддерживает опциональный заголовок Idempotency-Key для предотвращения дублирования заказов при повторных запросах.

Тело запроса

НазваниеТипОбязательныйОписание
product_idintegerНетСтабильный ID продукта точного слота тарифа для прямого заказа. Укажите ЛИБО его, ЛИБО catalog_product_id, но не оба.
catalog_product_idintegerНетМаршрутизируемый umbrella-ID страны+платформы. Сервер выбирает текущий подходящий тариф. Укажите его или product_id.
operator_idintegerНетНеобязательный ID оператора из /catalog/operators. Допустимо только с catalog_product_id; для Any не указывайте.
min_priceintegerНетНеобязательный нижний предел цены. Целое число IDR. Допустимо только с catalog_product_id.
max_priceintegerНетНеобязательный верхний предел цены. Целое число IDR. Допустимо только с catalog_product_id.
prefer_providerstringНетНеобязательный код провайдера, предпочтительный при равных предложениях.
policystringНетНеобязательная политика маршрутизации, действует только с catalog_product_id. Значения: cheapest (по умолчанию) выбирает самое дешёвое исправное предложение; best_success сначала ранжирует предложения по недавней успешности доставки. best_success оценивает каждого провайдера по доле заказов, получивших OTP, за последние 30 завершённых дней, по полосам в 10%, и учитывает провайдера только при наличии не менее 20 заказов за этот период — провайдеры ниже этого порога или без истории считаются нейтральными, поэтому новые предложения никогда не остаются без шанса (по запросу; сигнал стартует с нейтрального значения). Если также задан prefer_provider, предпочитаемый провайдер всё равно идёт первым.
quantityintegerНетКоличество (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 route by catalog_product_id — the server picks a current tier.
# product_id is the stable exact tier-slot id. catalog_product_id is the
# country+platform umbrella for routed ordering. Optional min_price/max_price
# bound the tier; operator_id scopes to a carrier from /catalog/operators.
# 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,"min_price":12000,"max_price":20000,"operator_id":433}'
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()

Пример ответа

200 OK
{
  "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,
        "operator_id": null,
        "operator_name": null
      }
    ],
    "failed_count": 0
  }
}
POST/orders/cancel

Отмена активного заказа. Стоимость аренды возвращается на баланс аккаунта.

Тело запроса

НазваниеТипОбязательныйОписание
idintegerДа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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": 15000,
    "new_balance": 515000
  }
}
POST/orders/finish

Отметить заказ как завершённый после получения OTP. Номер освобождается сразу, без ожидания истечения срока.

Тело запроса

НазваниеТипОбязательныйОписание
idintegerДа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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "COMPLETED"
  }
}
POST/orders/resend

Запросить повторную отправку SMS на арендованный номер. Не все платформы поддерживают повторную отправку — проверяйте поле resent в ответе.

Тело запроса

НазваниеТипОбязательныйОписание
idintegerДа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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "ACTIVE",
    "resent": true
  }
}
POST/orders/reactivate

Реактивирует завершённый номер — заказывает тот же номер повторно для нового кода подтверждения, не арендуя новый. Подходит только завершённый заказ, номер которого поддерживает реактивацию (проверьте can_reactivate у заказа или получите предпросмотр через reactivate-options). Реактивированный дочерний заказ — это НОВЫЙ заказ, возвращаемый в том же формате, что и create; баланс списывается автоматически.

Тело запроса

НазваниеТипОбязательныйОписание
idintegerДаЗавершённый заказ для реактивации.
max_priceintegerНетНеобязательный верхний предел стоимости. Целое число IDR. Реактивация отклоняется с 422 VALIDATION_ERROR, если текущая стоимость его превышает.

Как и create, это денежная операция — передайте заголовок Idempotency-Key для безопасного повтора (create и reactivate никогда не конфликтуют по одному ключу). Повторное использование ключа с другим телом возвращает 422 IDEMPOTENCY_KEY_REUSED, а ещё выполняющийся запрос с этим ключом — 409 REQUEST_IN_PROGRESS. Номер, который нельзя реактивировать, возвращает 409 CONFLICT; недостаточный баланс — 409 INSUFFICIENT_BALANCE.

Пример запроса

curl -s -X POST https://api.smscode.gg/v1/orders/reactivate \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-901" \
  -d '{"id":1001,"max_price":20000}'
const res = await fetch("https://api.smscode.gg/v1/orders/reactivate", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
    "Idempotency-Key": "unique-request-id-901",
  },
  body: JSON.stringify({ id: 1001, max_price: 20000 }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v1/orders/reactivate",
    json={"id": 1001, "max_price": 20000},
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-901",
    })
data = res.json()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 1044,
        "status": "ACTIVE",
        "product_id": 142,
        "catalog_product_id": 87,
        "amount": 15000,
        "phone_number": "+6281234567890",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-02-25T11:20:00+00:00",
        "failed_reason": null,
        "operator_id": null,
        "operator_name": null
      }
    ],
    "failed_count": 0
  }
}
GET/orders/{id}/reactivate-options

Предпросмотр того, сколько будет стоить реактивация прямо сейчас. Только чтение — не расходует Idempotency-Key и ничего не создаёт. Возвращает стоимость как целое число IDR. Доступно только для завершённого заказа, номер которого поддерживает реактивацию.

Path-параметры

НазваниеТипОбязательныйОписание
idintegerДаID заказа, для которого нужно рассчитать предварительную стоимость реактивации (параметр пути).

Пример запроса

curl -s https://api.smscode.gg/v1/orders/1001/reactivate-options \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v1/orders/1001/reactivate-options", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v1/orders/1001/reactivate-options",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "cost": 15000
  }
}
GET/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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}
PATCH/webhook

Обновить URL и/или секрет webhook. Секрет генерируется автоматически при первой установке URL. Отправьте пустую строку для очистки. URL должен использовать HTTPS.

Тело запроса

НазваниеТипОбязательныйОписание
webhook_urlstringНетHTTPS URL для получения webhook-событий (пустая строка для очистки)
webhook_secretstringНетОбщий секрет для подписи 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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}
POST/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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "status_code": 200
  }
}

Webhook-уведомления

Настройте webhook URL для получения push-уведомлений в реальном времени о событиях заказов вместо периодического опроса. Рекомендуемый подход для бот-скриптов.

События

СобытиеТриггер
order.otp_receivedПолучено новое SMS; распознанный код может быть null
order.completedЗаказ отмечен как завершённый (вручную или по истечении срока)
order.expiredЗаказ истёк до получения SMS (баланс возвращён)
order.canceledЗаказ отменён пользователем (баланс возвращён)

Каждое новое SMS создаёт это событие. otp_code может быть null при наличии otp_message. Несколько SMS-событий могут прийти не по порядку; используйте sms_revision, чтобы игнорировать более старую агрегированную пару.

Тело запроса

Тело POST-запроса webhook
{
  "event": "order.otp_received",
  "timestamp": "2026-02-25T12:00:00+00:00",
  "data": {
    "order_id": 1001,
    "phone_number": "+628123456789",
    "otp_code": null,
    "otp_message": "Confirm your login: https://example.com/confirm",
    "sms_revision": 1,
    "product_id": 142,
    "catalog_product_id": 87,
    "country": "Indonesia",
    "platform": "WhatsApp"
  }
}

Проверка подписи

Каждый webhook-запрос содержит заголовок X-Webhook-Signature с HMAC-SHA256 подписью тела запроса, используя ваш webhook_secret в качестве ключа:

X-Webhook-Signature:sha256={HMAC-SHA256(body, webhook_secret)}

Проверяйте эту подпись на своём сервере для подтверждения подлинности запроса. Доставка — однократная, с таймаутом 3 секунды, без повторов.

Лимиты запросов

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 секунд
Тест webhook10 запросов60 секунд

Коды ошибок

Ответы с ошибкой содержат один из следующих кодов в error.code:

КодHTTPОписание
UNAUTHORIZED401Отсутствует или недействителен API-токен
FORBIDDEN403Доступ запрещён
NOT_FOUND404Ресурс не найден (заказ, обменный курс и т.д.)
CONFLICT409Дублирующий запрос или конфликт ресурсов
INSUFFICIENT_BALANCE409Недостаточно средств на балансе
VALIDATION_ERROR422Параметры запроса не прошли валидацию
RATE_LIMIT_EXCEEDED429Слишком много запросов (проверьте заголовок Retry-After)
INTERNAL_ERROR500Внутренняя ошибка сервера
PROVIDER_ERROR422Вышестоящий SMS-провайдер отклонил запрос. При сбоях создания заказа ошибка может содержать details: cause_counts (заказы со старым product_id — сводка с группировкой по причине) или attempts (заказы с catalog_product_id — результаты по каждой попытке), используя значения ok, no_numbers, insufficient_balance, price_rejected, provider_unavailable, provider_account_balance, provider_error.
NO_OFFER_AVAILABLE422Нет активного предложения, подходящего под запрошенный продукт и правила (лимит цены, наличие).
CANCEL_TOO_EARLY409Заказ слишком новый для отмены — подождите 2 минуты
REQUEST_IN_PROGRESS409Запрос на создание с этим ключом идемпотентности ещё выполняется
IDEMPOTENCY_KEY_REUSED422Этот ключ идемпотентности уже использовался с другим телом запроса
SERVICE_UNAVAILABLE503Сервис временно недоступен (обслуживание)

Обзор

Все денежные поля в 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.

Миграция с v1 на v2

Аутентификация

Все API-запросы требуют Bearer token. Сгенерируйте его в разделе Настройки аккаунта в личном кабинете и включайте в каждый запрос:

Authorization:Bearer YOUR_API_TOKEN

Запросы без валидного токена получат ответ 401 UNAUTHORIZED.

Базовый URL

Все пути endpoint ниже указаны относительно:

https://api.smscode.gg/v2

Формат ответов

Каждый ответ возвращает JSON в едином формате. Все ответы содержат заголовок x-request-id для отладки.

Успех
{
  "success": true,
  "data": { ... }
}
Ошибка
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message"
  }
}

Ошибка v2: нет пригодного курса (503)

503 Service Unavailable · Retry-After: 60
{
  "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.

GET/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()

Пример ответа

200 OK
{
  "success": true,
  "data": [
    {
      "id": 6,
      "code": "ID",
      "name": "Indonesia",
      "dial_code": "+62",
      "emoji": "🇮🇩",
      "active": true
    }
  ]
}

Идентично v1 — меняется только базовый путь (/v1/v2).

GET/catalog/services

Возвращает список доступных сервисов (платформ). Опционально можно фильтровать по стране.

Query-параметры

НазваниеТипОбязательныйОписание
country_idintegerНетФильтрация сервисов для указанной страны

Пример запроса

curl -s "https://api.smscode.gg/v2/catalog/services?country_id=7" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/catalog/services?country_id=7", {
  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": 7},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Пример ответа

200 OK
{
  "success": true,
  "data": [
    {
      "id": 3,
      "code": "wa",
      "name": "WhatsApp",
      "active": true
    }
  ]
}

Идентично v1 — меняется только базовый путь (/v1/v2).

GET/catalog/operators

Возвращает операторов, доступных для выбора для страны + сервиса. Если доступны и реальные операторы, и запас Any, ответ включает строку Any с operator_id null; если продуктов для конкретных операторов нет, список пуст.

Query-параметры

НазваниеТипОбязательныйОписание
country_idintegerДаID страны
platform_idintegerДаID платформы/сервиса

Пример запроса

curl -s "https://api.smscode.gg/v2/catalog/operators?country_id=7&platform_id=3" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "7", platform_id: "3",
});
const res = await fetch(`https://api.smscode.gg/v2/catalog/operators?${params}`, {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/catalog/operators",
    params={"country_id": 7, "platform_id": 3},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Пример ответа

200 OK
{
  "success": true,
  "data": [
    {
      "operator_id": null,
      "code": "any",
      "name": "Any",
      "local_name": null
    },
    {
      "operator_id": 433,
      "code": "axis",
      "name": "AXIS (XL Axiata)",
      "local_name": "AXIS (XL Axiata)"
    }
  ]
}

Идентично v1 — меняется только базовый путь (/v1/v2).

GET/catalog/products

Возвращает постраничный список доступных продуктов. Фильтруйте по стране, платформе и при необходимости по оператору.

Query-параметры

НазваниеТипОбязательныйОписание
country_idintegerНетФильтр по ID страны
platform_idintegerНетФильтр по ID платформы/сервиса
operator_idintegerНетНеобязательный ID оператора из /catalog/operators. Не указывайте для продуктов Any.
sortstringНетПорядок сортировки: price_asc (по умолчанию), price_desc, available_asc, available_desc, name_asc, name_desc
limitintegerНетРезультатов на странице (1-10 000, по умолчанию 1 000)
pageintegerНетНомер страницы (мин. 1, по умолчанию 1)

Пример запроса

curl -s "https://api.smscode.gg/v2/catalog/products?country_id=7&platform_id=3&limit=10&page=1" \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const params = new URLSearchParams({
  country_id: "7", 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": 7, "platform_id": 3, "limit": 10, "page": 1},
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Пример ответа

200 OK
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 7,
      "platform_id": 3,
      "catalog_product_id": 87,
      "operator_id": null,
      "operator_name": null,
      "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 такого никогда не возвращает.

GET/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()

Пример ответа

200 OK
{
  "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, если пригодного курса нет.

GET/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()

Пример ответа

200 OK
{
  "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 такого никогда не возвращает.

GET/orders

Возвращает список заказов аутентифицированного пользователя, отсортированный по дате (новые первыми). Поддерживает фильтрацию по статусу и пагинацию через offset.

Query-параметры

НазваниеТипОбязательныйОписание
limitintegerНетМакс. результатов (1-100, по умолчанию 20)
offsetintegerНетКоличество пропускаемых результатов (по умолчанию 0)
statusstringНетФильтр по статусу: 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()

Пример ответа

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "ACTIVE",
      "created_at": "2026-02-25T10:00:00+00:00",
      "product_id": 142,
      "catalog_product_id": 87,
      "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,
      "operator_id": null,
      "operator_name": 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 такого никогда не возвращает.

GET/orders/{id}

Возвращает один заказ по ID. Доступны только заказы аутентифицированного пользователя.

Path-параметры

НазваниеТипОбязательныйОписание
idintegerДа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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "id": 1001,
    "status": "OTP_RECEIVED",
    "created_at": "2026-02-25T10:00:00+00:00",
    "product_id": 142,
    "catalog_product_id": 87,
    "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,
    "operator_id": null,
    "operator_name": 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 такого никогда не возвращает.

GET/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()

Пример ответа

200 OK
{
  "success": true,
  "data": [
    {
      "id": 1001,
      "status": "OTP_RECEIVED",
      "otp_code": null,
      "otp_message": "Confirm your login: https://example.com/confirm",
      "sms_revision": 1,
      "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,
      "sms_revision": 0,
      "otp_received_at": null,
      "expires_at": "2026-02-25T10:50:00+00:00",
      "failed_reason": null
    }
  ]
}

v2: этот endpoint не является денежным — он не возвращает ни amount, ни meta.fx (та же структура, что и в v1, но в /v2).

POST/orders/create

Создаёт новый заказ виртуального номера. Баланс списывается автоматически. Поддерживает опциональный заголовок Idempotency-Key для предотвращения дублирования заказов при повторных запросах.

Тело запроса

НазваниеТипОбязательныйОписание
product_idintegerНетСтабильный ID продукта точного слота тарифа для прямого заказа. Укажите ЛИБО его, ЛИБО catalog_product_id, но не оба.
catalog_product_idintegerНетМаршрутизируемый umbrella-ID страны+платформы. Сервер выбирает текущий подходящий тариф. Укажите его или product_id.
operator_idintegerНетНеобязательный ID оператора из /catalog/operators. Допустимо только с catalog_product_id; для Any не указывайте.
min_pricestringНетНеобязательный нижний предел цены. Десятичная строка USD (например, "0.30"). Допустимо только с catalog_product_id.
max_pricestringНетНеобязательный верхний предел цены. Десятичная строка USD (например, "0.50"). Допустимо только с catalog_product_id.
prefer_providerstringНетНеобязательный код провайдера, предпочтительный при равных предложениях.
policystringНетНеобязательная политика маршрутизации, действует только с catalog_product_id. Значения: cheapest (по умолчанию) выбирает самое дешёвое исправное предложение; best_success сначала ранжирует предложения по недавней успешности доставки. best_success оценивает каждого провайдера по доле заказов, получивших OTP, за последние 30 завершённых дней, по полосам в 10%, и учитывает провайдера только при наличии не менее 20 заказов за этот период — провайдеры ниже этого порога или без истории считаются нейтральными, поэтому новые предложения никогда не остаются без шанса (по запросу; сигнал стартует с нейтрального значения). Если также задан prefer_provider, предпочитаемый провайдер всё равно идёт первым.
quantityintegerНетКоличество (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 '{"catalog_product_id":87,"min_price":"0.30","max_price":"0.50","operator_id":433,"quantity":1}'
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({
    catalog_product_id: 87,
    min_price: "0.30",
    max_price: "0.50",
    operator_id: 433,
    quantity: 1,
  }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/create",
    json={
        "catalog_product_id": 87,
        "min_price": "0.30",
        "max_price": "0.50",
        "operator_id": 433,
        "quantity": 1,
    },
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-123",
    })
data = res.json()

Пример ответа

200 OK
{
  "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,
        "operator_id": null,
        "operator_name": 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 такого никогда не возвращает.

POST/orders/cancel

Отмена активного заказа. Стоимость аренды возвращается на баланс аккаунта.

Тело запроса

НазваниеТипОбязательныйОписание
idintegerДа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()

Пример ответа

200 OK
{
  "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 такого никогда не возвращает.

POST/orders/finish

Отметить заказ как завершённый после получения OTP. Номер освобождается сразу, без ожидания истечения срока.

Тело запроса

НазваниеТипОбязательныйОписание
idintegerДа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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "COMPLETED"
  }
}

Идентично v1 — меняется только базовый путь (/v1/v2).

POST/orders/resend

Запросить повторную отправку SMS на арендованный номер. Не все платформы поддерживают повторную отправку — проверяйте поле resent в ответе.

Тело запроса

НазваниеТипОбязательныйОписание
idintegerДа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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "ACTIVE",
    "resent": true
  }
}

Идентично v1 — меняется только базовый путь (/v1/v2).

POST/orders/reactivate

Реактивирует завершённый номер — заказывает тот же номер повторно для нового кода подтверждения, не арендуя новый. Подходит только завершённый заказ, номер которого поддерживает реактивацию (проверьте can_reactivate у заказа или получите предпросмотр через reactivate-options). Реактивированный дочерний заказ — это НОВЫЙ заказ, возвращаемый в том же формате, что и create; баланс списывается автоматически.

Тело запроса

НазваниеТипОбязательныйОписание
idintegerДаЗавершённый заказ для реактивации.
max_pricestringНетНеобязательный верхний предел стоимости. Десятичная строка USD (например, "0.50"). Реактивация отклоняется с 422 VALIDATION_ERROR, если текущая стоимость его превышает.

Как и create, это денежная операция — передайте заголовок Idempotency-Key для безопасного повтора (create и reactivate никогда не конфликтуют по одному ключу). Повторное использование ключа с другим телом возвращает 422 IDEMPOTENCY_KEY_REUSED, а ещё выполняющийся запрос с этим ключом — 409 REQUEST_IN_PROGRESS. Номер, который нельзя реактивировать, возвращает 409 CONFLICT; недостаточный баланс — 409 INSUFFICIENT_BALANCE.

Пример запроса

curl -s -X POST https://api.smscode.gg/v2/orders/reactivate \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: unique-request-id-901" \
  -d '{"id":1001,"max_price":"0.50"}'
const res = await fetch("https://api.smscode.gg/v2/orders/reactivate", {
  method: "POST",
  headers: {
    Authorization: "Bearer YOUR_API_TOKEN",
    "Content-Type": "application/json",
    "Idempotency-Key": "unique-request-id-901",
  },
  body: JSON.stringify({ id: 1001, max_price: "0.50" }),
});
const data = await res.json();
import requests

res = requests.post("https://api.smscode.gg/v2/orders/reactivate",
    json={"id": 1001, "max_price": "0.50"},
    headers={
        "Authorization": "Bearer YOUR_API_TOKEN",
        "Idempotency-Key": "unique-request-id-901",
    })
data = res.json()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "orders": [
      {
        "id": 1044,
        "status": "ACTIVE",
        "product_id": 142,
        "catalog_product_id": 87,
        "amount": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "canonical_currency": "IDR" },
        "phone_number": "+6281234567890",
        "otp_code": null,
        "otp_received_at": null,
        "expires_at": "2026-02-25T11:20:00+00:00",
        "failed_reason": null,
        "operator_id": null,
        "operator_name": 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 такого никогда не возвращает.

GET/orders/{id}/reactivate-options

Предпросмотр того, сколько будет стоить реактивация прямо сейчас. Только чтение — не расходует Idempotency-Key и ничего не создаёт. Возвращает стоимость как денежный объект USD с квитанцией FX. Доступно только для завершённого заказа, номер которого поддерживает реактивацию.

Path-параметры

НазваниеТипОбязательныйОписание
idintegerДаID заказа, для которого нужно рассчитать предварительную стоимость реактивации (параметр пути).

Пример запроса

curl -s https://api.smscode.gg/v2/orders/1001/reactivate-options \
  -H "Authorization: Bearer YOUR_API_TOKEN"
const res = await fetch("https://api.smscode.gg/v2/orders/1001/reactivate-options", {
  headers: { Authorization: "Bearer YOUR_API_TOKEN" },
});
const data = await res.json();
import requests

res = requests.get("https://api.smscode.gg/v2/orders/1001/reactivate-options",
    headers={"Authorization": "Bearer YOUR_API_TOKEN"})
data = res.json()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "cost": { "amount": "0.9231", "currency": "USD", "canonical_amount": 15000, "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 такого никогда не возвращает.

GET/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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}

Идентично v1 — меняется только базовый путь (/v1/v2).

PATCH/webhook

Обновить URL и/или секрет webhook. Секрет генерируется автоматически при первой установке URL. Отправьте пустую строку для очистки. URL должен использовать HTTPS.

Тело запроса

НазваниеТипОбязательныйОписание
webhook_urlstringНетHTTPS URL для получения webhook-событий (пустая строка для очистки)
webhook_secretstringНетОбщий секрет для подписи 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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "webhook_url": "https://example.com/webhook",
    "webhook_secret": "a1b2c3d4e5f6..."
  }
}

Идентично v1 — меняется только базовый путь (/v1/v2).

POST/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()

Пример ответа

200 OK
{
  "success": true,
  "data": {
    "status_code": 200
  }
}

Идентично v1 — меняется только базовый путь (/v1/v2).

Webhook-уведомления

Настройте webhook URL для получения push-уведомлений в реальном времени о событиях заказов вместо периодического опроса. Рекомендуемый подход для бот-скриптов.

События

СобытиеТриггер
order.otp_receivedПолучено новое SMS; распознанный код может быть null
order.completedЗаказ отмечен как завершённый (вручную или по истечении срока)
order.expiredЗаказ истёк до получения SMS (баланс возвращён)
order.canceledЗаказ отменён пользователем (баланс возвращён)

Каждое новое SMS создаёт это событие. otp_code может быть null при наличии otp_message. Несколько SMS-событий могут прийти не по порядку; используйте sms_revision, чтобы игнорировать более старую агрегированную пару.

Тело запроса

Тело POST-запроса webhook
{
  "event": "order.otp_received",
  "timestamp": "2026-02-25T12:00:00+00:00",
  "data": {
    "order_id": 1001,
    "phone_number": "+628123456789",
    "otp_code": null,
    "otp_message": "Confirm your login: https://example.com/confirm",
    "sms_revision": 1,
    "product_id": 142,
    "catalog_product_id": 87,
    "country": "Indonesia",
    "platform": "WhatsApp"
  }
}

Проверка подписи

Каждый webhook-запрос содержит заголовок X-Webhook-Signature с HMAC-SHA256 подписью тела запроса, используя ваш webhook_secret в качестве ключа:

X-Webhook-Signature:sha256={HMAC-SHA256(body, webhook_secret)}

Проверяйте эту подпись на своём сервере для подтверждения подлинности запроса. Доставка — однократная, с таймаутом 3 секунды, без повторов.

Лимиты запросов

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 секунд
Тест webhook10 запросов60 секунд

Коды ошибок

Ответы с ошибкой содержат один из следующих кодов в error.code:

КодHTTPОписание
UNAUTHORIZED401Отсутствует или недействителен API-токен
FORBIDDEN403Доступ запрещён
NOT_FOUND404Ресурс не найден (заказ, обменный курс и т.д.)
CONFLICT409Дублирующий запрос или конфликт ресурсов
INSUFFICIENT_BALANCE409Недостаточно средств на балансе
VALIDATION_ERROR422Параметры запроса не прошли валидацию
RATE_LIMIT_EXCEEDED429Слишком много запросов (проверьте заголовок Retry-After)
INTERNAL_ERROR500Внутренняя ошибка сервера
PROVIDER_ERROR422Вышестоящий SMS-провайдер отклонил запрос. При сбоях создания заказа ошибка может содержать details: cause_counts (заказы со старым product_id — сводка с группировкой по причине) или attempts (заказы с catalog_product_id — результаты по каждой попытке), используя значения ok, no_numbers, insufficient_balance, price_rejected, provider_unavailable, provider_account_balance, provider_error.
NO_OFFER_AVAILABLE422Нет активного предложения, подходящего под запрошенный продукт и правила (лимит цены, наличие).
CANCEL_TOO_EARLY409Заказ слишком новый для отмены — подождите 2 минуты
REQUEST_IN_PROGRESS409Запрос на создание с этим ключом идемпотентности ещё выполняется
IDEMPOTENCY_KEY_REUSED422Этот ключ идемпотентности уже использовался с другим телом запроса
SERVICE_UNAVAILABLE503Сервис временно недоступен (обслуживание)
FX_RATE_UNAVAILABLE503Обменный курс USD/IDR недоступен (денежные endpoint'ы v2) — возвращается 503 с заголовком Retry-After.
v1 → v2

Миграция с v1 на v2

v1 работает с IDR; v2 работает с USD. Обе версии сосуществуют постоянно — отключения не планируется. Выберите одну версию на интеграцию; не смешивайте базовые пути. v2 идентична v1, отличается лишь способом представления денег.

Аспектv1 · IDRv2 · USD
Денежные поляЦелое IDR, напр. 15000Денежный объект { amount, currency, canonical_amount, canonical_currency }
meta.fxОтсутствуетОбязателен в каждом денежном ответе
ВалютаIDRUSD (зашит жёстко)
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)

Примеры рядом

GET/balance
v1
{
  "success": true,
  "data": {
    "currency": "IDR",
    "balance": 500000
  }
}
v2
{
  "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" } }
}
GET/catalog/products
v1
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 7,
      "platform_id": 3,
      "catalog_product_id": 87,
      "operator_id": null,
      "operator_name": null,
      "available": 42,
      "price": 15000,
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1 }
}
v2
{
  "success": true,
  "data": [
    {
      "id": 142,
      "name": "WhatsApp Indonesia",
      "country_id": 7,
      "platform_id": 3,
      "catalog_product_id": 87,
      "operator_id": null,
      "operator_name": null,
      "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" } }
}

product_id — стабильный ID слота тарифа SMSCode. Сохраняйте его, если хотите заказать именно этот тариф; цена и доступность могут меняться в той же строке. catalog_product_id — стабильный umbrella-идентификатор страны+платформы для маршрутизируемого заказа; используйте его с необязательными operator_id, min_price, max_price, prefer_provider и policy, когда хотите, чтобы сервер выбрал текущий подходящий тариф.

POST/orders/cancel
v1
{
  "success": true,
  "data": {
    "order_id": 1001,
    "status": "CANCELED",
    "refund_amount": 15000,
    "new_balance": 515000
  }
}
v2
{
  "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" } }
}

Чек-лист миграции

  1. Замените базовый путь /v1/v2.
  2. Разбирайте денежные поля как объекты — читайте amount как десятичную строку; currency равно "USD".
  3. Для сверки реестра используйте canonical_amount (точный IDR); сумма в USD amount — это проекция во время рендеринга, а rate раскрывается один раз в meta.fx.
  4. Обрабатывайте новый FX_RATE_UNAVAILABLE (503) — повторите запрос после Retry-After. v1 такого никогда не возвращает.