API поставщика наборов карточек

Отправка наборов карточек Steam покупателю по trade-ссылке. Подходит для своих ботов, сайтов, магазинов и плагинов.

Базовый адрес: https://steamlvlup.win · Формат: JSON · Кодировка: UTF-8

Быстрый старт

  1. Откройте бота, пополните баланс и возьмите ключ в разделе 🔑 API.
  2. Отправьте заказ:
curl -X POST https://steamlvlup.win/v1/order \
  -H "X-Api-Key: sk_ваш_ключ" \
  -H "Content-Type: application/json" \
  -d '{"order_id":"A1B2C3","trade_url":"https://steamcommunity.com/tradeoffer/new/?partner=123456789&token=AbCdEfGh","sets_count":5}'

В ответ придёт ссылка на обмен — передайте её покупателю. Деньги спишутся, когда он примет обмен.

Для FunPay Cardinal есть готовый плагин — скачайте его в боте (🔑 API → 📥 Скачать плагин), ключ уже будет внутри.

Авторизация

Ключ передаётся в заголовке X-Api-Key в каждом запросе.

X-Api-Key: sk_8f2c...e91a

Деньги и холд

Оплачиваются только наборы, которые покупатель реально получил.

СобытиеЧто происходит с балансом
Обмен отправленСумма наборы × цена замораживается (холд). Если отправлено меньше, чем просили, — разница сразу возвращается.
Покупатель принялХолд списывается. Статус заказа → accepted.
Отклонил / отменён / истёкХолд возвращается на баланс. Статус → refunded.
Ошибка до отправкиНичего не списывается. Статус → failed.

Непринятый обмен автоматически отменяется через несколько часов, деньги возвращаются.

Повторы и дубли

Всегда передавайте order_id — уникальный id заказа в вашей системе (например, id заказа FunPay).

Дополнительно сервис помнит, какие наборы уже отправлялись покупателю, и не шлёт игры сверх 5 уровня значка — даже если Steam ещё не обновил значки.

Лимиты

Создать заказ

POST/v1/order

Подбирает наборы под значки покупателя, отправляет обмен и замораживает оплату.

Тело запроса

ПолеТипОписание
trade_urlstringобязательноTrade-ссылка покупателя с partner и token
sets_countintобязательноСколько наборов отправить, 1–500
order_idstringрекомендуетсяВаш id заказа — защита от дублей, поиск, история
buyer_namestringИмя/ник покупателя — для истории и споров

Если наборов на складе или денег на балансе меньше, чем запрошено, отправляется сколько возможно (partial: true).

Ответ — успех

{
  "success": true,
  "trade_id": "8123456789",
  "offer_url": "https://steamcommunity.com/tradeoffer/8123456789/",
  "sets": 5,
  "requested": 5,
  "partial": false,
  "limited_by": null,          // "stock" | "balance" — если partial
  "charged": "$1.50",          // заморожено до принятия обмена
  "balance": "$18.50",         // остаток после заморозки
  "duplicate": false           // true — это повтор уже созданного заказа
}

Ответ — ошибка

{
  "success": false,
  "error_code": "50",
  "error": "Покупатель не может обмениваться (бан/холд/лимит/смена ника)",
  "buyer_message": "Ваш аккаунт Steam сейчас не может участвовать в обменах...",
  "retryable": false
}

buyer_message — готовый текст для покупателя. retryable: true — покупатель может исправить сам (открыть инвентарь, прислать новую ссылку), после этого повторите запрос с тем же order_id. Полный список — в разделе Ошибки.

Проверить ссылку

POST/v1/quote

Сколько наборов подходит этому покупателю — без отправки и без списания. Удобно показать покупателю до оплаты.

// запрос
{ "trade_url": "https://steamcommunity.com/tradeoffer/new/?partner=123456789&token=AbCdEfGh" }

// ответ
{
  "success": true,
  "available": 120,       // подходящих наборов на складе для этого аккаунта
  "affordable": 61,       // на сколько хватает вашего баланса
  "max_now": 61,          // min(available, affordable)
  "price_per_set": "$0.30"
}

Статус заказа

GET/v1/order/{order_id}

Результат по вашему order_id. Используйте после таймаута и чтобы узнать, принял ли покупатель обмен.

{
  "success": true,
  "order": {
    "id": 482,
    "order_id": "A1B2C3",
    "buyer_name": "G63ONLINE",
    "status": "accepted",
    "sets": 5,
    "requested": 5,
    "price_per_set": "$0.30",
    "amount": "$1.50",
    "trade_id": "8123456789",
    "offer_url": "https://steamcommunity.com/tradeoffer/8123456789/",
    "error": null,
    "created_at": "2026-10-07T13:44:13.000Z",
    "accepted_at": "2026-10-07T13:46:02.000Z",
    "closed_at": null
  }
}

Не найден — HTTP 404, {"success": false, "error_code": "not_found"}.

История заказов

GET/v1/orders?limit=20&offset=0

Заказы, новые сначала. limit — до 100.

{ "success": true, "total": 134, "limit": 20, "offset": 0, "orders": [ { ...как в /v1/order/{id} } ] }

Баланс и статистика

GET/v1/me
{
  "success": true,
  "balance": "$18.50",
  "hold": "$1.50",
  "price_per_set": "$0.30",
  "stats": {
    "today": { "orders": 3,   "sets": 15,   "spent": "$4.50" },
    "week":  { "orders": 21,  "sets": 98,   "spent": "$29.40" },
    "month": { "orders": 80,  "sets": 412,  "spent": "$123.60" },
    "all":   { "orders": 134, "sets": 690,  "spent": "$207.00" },
    "pending": { "orders": 1, "sets": 5 }
  }
}

Статистика считает только принятые обмены.

Состояние сервиса

GET/v1/health
{ "success": true, "ready": true, "balance": "$18.50", "price_per_set": "$0.30" }

ready: false — склад временно недоступен (Steam переподключается). Заказы в это время отклоняются с engine_down, деньги не трогаются.

Статусы заказа

statusЗначениеДеньги
pendingОтправляетсяв холде
sentОбмен отправлен, ждёт покупателяв холде
acceptedПокупатель принял — финальныйсписаны
refundedНе принят (отклонён/отменён/истёк) — финальныйвозвращены
failedНе отправлен — финальный, можно повторить с тем же order_idне списывались

Ошибки

HTTP-коды

КодКогда
200Запрос обработан. Смотрите поле success — бизнес-ошибки заказа приходят с кодом 200 и success: false
401bad_key — нет ключа или он неверный
403banned — доступ заблокирован
404not_found — заказа нет
429rate_limit — слишком много запросов, подождите минуту

Коды error_code

error_codeЧто случилосьЧто делать
bad_linkTrade-ссылка неверная (нет partner/token)Попросить у покупателя новую ссылку
bad_countsets_count меньше 1Исправить запрос
too_manyБольше 500 наборов за разРазбить на несколько заказов
insufficient_balanceНе хватает даже на 1 наборПополнить баланс в боте
out_of_stockНет наборов, подходящих этому покупателю (значки прокачаны или склад пуст)Сообщить покупателю, вернуть деньги или подождать пополнения
in_progressЗаказ ещё обрабатывается (долгая очередь) или с этим order_id уже идёт обработка. При pending: true — это нормально, обмен скорее всего будет отправленНе повторять. Опрашивать GET /v1/order/{order_id} до sent / failed
timeoutSteam не ответил вовремя — обмен, скорее всего, созданНе повторять. Проверить статус через 1–2 минуты
engine_downСклад временно недоступенПовторить позже с тем же order_id
bannedДоступ заблокированСвязаться с поддержкой
internalВнутренняя ошибкаПовторить позже, сообщить в поддержку

Ошибки Steam (числовые коды)

error_codeПричинаretryable
15, 25Инвентарь покупателя закрытда — откроет и пришлёт ссылку снова
26Ссылка устарела / недействительнада — нужна новая ссылка
34Аккаунт не найден (недавняя смена ника)да
20У покупателя слишком много активных обменовда
40Временная ошибка Steamда
11Не подключён мобильный Steam Guardнет
24Временное ограничение обменов (смена пароля / Guard)нет
50Аккаунт не может обмениваться: трейд-бан, холд, смена пароля или ника, свежий Guardнет
16Таймаут Steam — см. timeoutнет, проверить статус

Для всех этих ошибок в ответе есть buyer_message — понятное объяснение для покупателя со ссылкой на проверку статуса аккаунта.

Рекомендуемый алгоритм

1. Получили заказ у себя → order_id = ваш id
2. (необязательно) POST /v1/quote — показать покупателю, сколько наборов ему подойдёт
3. POST /v1/order  {order_id, trade_url, sets_count, buyer_name}
   ├─ success: true          → отдать покупателю offer_url
   │                            partial: true → сообщить, что отправлено sets из requested
   ├─ retryable: true        → показать buyer_message, дождаться новой ссылки,
   │                            повторить с ТЕМ ЖЕ order_id
   ├─ in_progress / timeout  → НЕ повторять; опрашивать GET /v1/order/{order_id}
   │                            каждые 10–15 сек до sent / failed
   ├─ сетевая ошибка/таймаут → то же: GET /v1/order/{order_id}, а не новый заказ
   └─ прочие ошибки          → показать buyer_message, обработать вручную
4. Узнать, принят ли обмен:  GET /v1/order/{order_id} → status accepted / refunded

Примеры кода

import requests

API = "https://steamlvlup.win"
HEAD = {"X-Api-Key": "sk_ваш_ключ", "User-Agent": "MyShop/1.0"}

def send_sets(order_id, trade_url, sets):
    try:
        r = requests.post(f"{API}/v1/order", headers=HEAD, timeout=90, json={
            "order_id": order_id, "trade_url": trade_url, "sets_count": sets,
        }).json()
    except requests.RequestException:
        # связь оборвалась — НЕ создаём новый заказ, проверяем этот
        return requests.get(f"{API}/v1/order/{order_id}", headers=HEAD, timeout=30).json()

    if r["success"]:
        print("Обмен:", r["offer_url"], "наборов:", r["sets"])
    elif r.get("retryable"):
        print("Покупателю:", r["buyer_message"])   # исправит и пришлёт ссылку снова
    else:
        print("Ошибка", r["error_code"], r["error"])
    return r
const API = "https://steamlvlup.win";
const HEAD = { "X-Api-Key": "sk_ваш_ключ", "User-Agent": "MyShop/1.0", "Content-Type": "application/json" };

async function sendSets(orderId, tradeUrl, sets) {
  try {
    const res = await fetch(`${API}/v1/order`, {
      method: "POST", headers: HEAD,
      body: JSON.stringify({ order_id: orderId, trade_url: tradeUrl, sets_count: sets }),
      signal: AbortSignal.timeout(90_000),
    });
    const r = await res.json();
    if (r.success) console.log("Обмен:", r.offer_url, "наборов:", r.sets);
    else if (r.retryable) console.log("Покупателю:", r.buyer_message);
    else console.log("Ошибка", r.error_code, r.error);
    return r;
  } catch {
    // таймаут/обрыв — проверяем статус этого же заказа
    return (await fetch(`${API}/v1/order/${orderId}`, { headers: HEAD })).json();
  }
}
<?php
$api = "https://steamlvlup.win";
$ch = curl_init("$api/v1/order");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_TIMEOUT => 90,
  CURLOPT_HTTPHEADER => ["X-Api-Key: sk_ваш_ключ", "Content-Type: application/json", "User-Agent: MyShop/1.0"],
  CURLOPT_POSTFIELDS => json_encode([
    "order_id" => "A1B2C3",
    "trade_url" => "https://steamcommunity.com/tradeoffer/new/?partner=123456789&token=AbCdEfGh",
    "sets_count" => 5,
  ]),
]);
$r = json_decode(curl_exec($ch), true);
if ($r["success"]) echo "Обмен: " . $r["offer_url"];
else echo ($r["buyer_message"] ?? $r["error"]);
# создать заказ
curl -X POST https://steamlvlup.win/v1/order -H "X-Api-Key: sk_ваш_ключ" -H "Content-Type: application/json" \
  -d '{"order_id":"A1B2C3","trade_url":"https://steamcommunity.com/tradeoffer/new/?partner=123456789&token=AbCdEfGh","sets_count":5}'

# статус
curl https://steamlvlup.win/v1/order/A1B2C3 -H "X-Api-Key: sk_ваш_ключ"

# сколько подойдёт покупателю
curl -X POST https://steamlvlup.win/v1/quote -H "X-Api-Key: sk_ваш_ключ" -H "Content-Type: application/json" \
  -d '{"trade_url":"https://steamcommunity.com/tradeoffer/new/?partner=123456789&token=AbCdEfGh"}'

# баланс
curl https://steamlvlup.win/v1/me -H "X-Api-Key: sk_ваш_ключ"

Вопросы по интеграции — поддержка · бот · главная