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

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

Обзор

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

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

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

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

Query-параметры

НазваниеТипОбязательныйОписание
country_idintegerНетФильтр по ID страны
platform_idintegerНетФильтр по ID платформы/сервиса
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=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()

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

200 OK
{
  "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 }
}
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,
      "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
    }
  ]
}
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,
    "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
  }
}
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": "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
    }
  ]
}
POST /orders/create

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

Тело запроса

НазваниеТипОбязательныйОписание
product_idintegerНетID продукта для заказа. Укажите ТОЛЬКО ОДНО: product_id ИЛИ catalog_product_id, но не оба.
catalog_product_idintegerНетСтабильный ID продукта — сервер сам подберёт лучшее текущее предложение (самое дешёвое, с учётом надёжности). Укажите либо это, либо product_id.
max_priceinteger · stringНетНеобязательный лимит цены. v1: целое число в IDR. v2: строка с десятичным USD (напр. "0.50").
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 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()

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

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
      }
    ],
    "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
  }
}
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_receivedOTP-код доставлен на арендованный номер
order.completedЗаказ отмечен как завершённый (вручную или по истечении срока)
order.expiredЗаказ истёк без OTP (баланс возвращён)
order.canceledЗаказ отменён пользователем (баланс возвращён)

Тело запроса

Тело POST-запроса webhook
{
  "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 в качестве ключа:

X-Webhook-Signature: sha256={HMAC-SHA256(body, 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 секунд
Тест 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-провайдер отклонил запрос. При сбоях создания заказа ошибка может содержать <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_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=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()

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

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

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

GET /catalog/products

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

Query-параметры

НазваниеТипОбязательныйОписание
country_idintegerНетФильтр по ID страны
platform_idintegerНетФильтр по ID платформы/сервиса
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=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()

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

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

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

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

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": "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).

POST /orders/create

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

Тело запроса

НазваниеТипОбязательныйОписание
product_idintegerНетID продукта для заказа. Укажите ТОЛЬКО ОДНО: product_id ИЛИ catalog_product_id, но не оба.
catalog_product_idintegerНетСтабильный ID продукта — сервер сам подберёт лучшее текущее предложение (самое дешёвое, с учётом надёжности). Укажите либо это, либо product_id.
max_priceinteger · stringНетНеобязательный лимит цены. v1: целое число в IDR. v2: строка с десятичным USD (напр. "0.50").
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 '{"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()

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

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
      }
    ],
    "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).

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_receivedOTP-код доставлен на арендованный номер
order.completedЗаказ отмечен как завершённый (вручную или по истечении срока)
order.expiredЗаказ истёк без OTP (баланс возвращён)
order.canceledЗаказ отменён пользователем (баланс возвращён)

Тело запроса

Тело POST-запроса webhook
{
  "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 в качестве ключа:

X-Webhook-Signature: sha256={HMAC-SHA256(body, 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 секунд
Тест 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-провайдер отклонил запрос. При сбоях создания заказа ошибка может содержать <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_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": 6,
      "platform_id": 3,
      "catalog_product_id": 87,
      "available": 42,
      "price": 15000,
      "active": true
    }
  ],
  "meta": { "page": 1, "limit": 10, "count": 1 }
}
v2
{
  "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 по-прежнему поддерживается и обратно совместим — этот идентификатор изменчив и меняется при каждом сдвиге ценового тарифа провайдера, поэтому совет запрашивать каталог заново перед заказом относится только к этому пути.

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