rentplay.games

API для партнёров

REST + JSON. Всё, что есть в кабинете, — покупка кодов, продление аренды, возврат — доступно из вашего бота или магазина. Машинное описание: openapi.json (OpenAPI 3.1 — импортируется в Postman и Swagger).

Ключ и адрес

Адрес: https://rentplay.games/api/v1. Ключ выпускается в кабинете → API и показывается один раз. Передавайте его заголовком Authorization: Bearer rp_live_… (или X-Api-Key). Ключ — это доступ к вашему балансу: держите его на сервере, не в браузере и не в коде клиентского бота. Утёк — отзовите в кабинете и выпустите новый.

curl https://rentplay.games/api/v1/me \
  -H "Authorization: Bearer $RENTPLAY_KEY"

# {"userId":"…","balanceRub":5000,"partner":{"status":"approved","discount":30},"scopes":["promo"],"bonusDailyLimitRub":null}

Предел — 120 запросов в минуту на ключ. Деньги в API — целые рубли, время — ISO 8601 в UTC.

Повторы и idempotencyKey

Каждый запрос, который тратит деньги (покупка, продление, бонус), несёт idempotencyKey — любую уникальную строку до 100 символов, удобнее всего UUID или номер заказа на FunPay. Если связь оборвалась и вы не знаете, прошёл ли запрос, — просто повторите его с тем же ключом: второй раз деньги не спишутся, вернётся результат первого (у покупки — те же коды и статус 200 вместо 201). Новая покупка — новый ключ.

Жизнь промокода

Если в момент ввода все аккаунты с игрой заняты, код не сгорает: покупатель увидит, когда освободится ближайший, и введёт код позже.

Методы

GET/me

Баланс, статус партнёра и скидка, права ключа.

GET/catalogscope: promo

Прайс: игры с offerId, сроки terms[] (минуты, розница, ваша оптовая цена) и наличие свободных аккаунтов прямо сейчас.

curl https://rentplay.games/api/v1/catalog -H "Authorization: Bearer $RENTPLAY_KEY"

# {"discount":30,"games":[{"offerId":"cm…","slug":"elden-ring","title":"Elden Ring","steamAppId":"1245620",
#   "terms":[{"minutes":1440,"label":"Сутки","retailRub":189,"wholesaleRub":132}, …],
#   "availability":{"free":3,"total":5,"nextFreeAt":null}}]}
POST/promocodesscope: promo

Купить от 1 до 100 кодов одной игры и срока. Игра — offerId или slug; срок — minutes из terms. Списывается wholesaleRub × quantity.

curl -X POST https://rentplay.games/api/v1/promocodes \
  -H "Authorization: Bearer $RENTPLAY_KEY" -H "Content-Type: application/json" \
  -d '{"slug":"elden-ring","minutes":1440,"quantity":2,"idempotencyKey":"funpay-order-48213"}'

# 201 {"repeated":false,"totalRub":264,"balanceRub":4736,
#      "codes":[{"code":"RP-7KQM-X2PD-9WHT","status":"active","priceRub":132,"expiresAt":"…","rental":null, …}, …]}
GET/promocodes?status=&limit=&before=scope: promo

Ваши коды, новые сверху. status: active | redeemed | refunded | expired; limit до 200. Следующая страница — before= из поля nextBefore ответа (null — дальше пусто).

GET/promocodes/{code}scope: promo

Один код: статус, когда активирован, идёт ли аренда и до скольких.

POST/promocodes/{code}/extendscope: promo

Продлить аренду по активированному коду за ваш баланс (по оптовой цене срока).

curl -X POST https://rentplay.games/api/v1/promocodes/RP-7KQM-X2PD-9WHT/extend \
  -H "Authorization: Bearer $RENTPLAY_KEY" -H "Content-Type: application/json" \
  -d '{"minutes":1440,"idempotencyKey":"funpay-order-48299"}'

# 200 {"code":"RP-7KQM-X2PD-9WHT","repeated":false,"rentalEndsAt":"2026-10-20T18:00:00.000Z","priceRub":132,"balanceRub":4604}
POST/promocodes/{code}/refundscope: promo

Вернуть неактивированный код — его цена вернётся на баланс. Повтор ответит 409, второго зачисления не будет.

curl -X POST https://rentplay.games/api/v1/promocodes/RP-7KQM-X2PD-9WHT/refund -H "Authorization: Bearer $RENTPLAY_KEY"

# 200 {"code":"RP-7KQM-X2PD-9WHT","status":"refunded","refundedRub":132,"balanceRub":4736}
GET/transactions?limit=&before=

Журнал вашего баланса: пополнения, покупки кодов, продления, возвраты. Курсор — как у списка кодов.

POST/bonusscope: bonus

Для пабликов и каналов: начислить бонус на баланс человека по его Telegram ID (строкой или числом). Если человека ещё нет на сайте — бонус дождётся его первого входа через Telegram. Право bonus и дневной предел выдаёт только администрация.

curl -X POST https://rentplay.games/api/v1/bonus \
  -H "Authorization: Bearer $RENTPLAY_KEY" -H "Content-Type: application/json" \
  -d '{"telegramUserId":"123456789","amountRub":50,"reason":"победа в конкурсе","idempotencyKey":"contest-12-user-123456789"}'

# 201 {"telegramUserId":"123456789","amountRub":50,"repeated":false}

Ошибки

Всегда JSON вида {"error":{"code":"…","message":"…"}}. Решения принимайте по HTTP-статусу и code; message — для человека.

401unauthorizedНет ключа, ключ неверный или отозван
402insufficient_fundsНе хватает баланса; в ответе needRub — сколько
403forbidden_scopeУ ключа нет нужного права (promo или bonus)
403not_partnerПартнёрство не подтверждено или приостановлено
404not_foundНет такой игры или кода (чужой код — тоже 404)
409not_activeПродление: код не активирован или аренда уже закончилась
409not_refundableВозврат: код уже активирован, истёк или возвращён
422validation_errorНеверные поля, срок аренды или битый JSON (invalid_json)
429rate_limitedБольше 120 запросов в минуту на ключ; заголовок Retry-After
429daily_limitБонусы: исчерпан дневной предел ключа
500internalСбой у нас — повторите запрос с тем же idempotencyKey

Пример на JavaScript (Node 18+)

Продали аренду на FunPay → купили код и отдали покупателю. Повтор после сбоя безопасен благодаря idempotencyKey.

const API = "https://rentplay.games/api/v1";
const KEY = process.env.RENTPLAY_KEY;

async function call(path, body) {
  const res = await fetch(API + path, {
    method: body ? "POST" : "GET",
    headers: { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" },
    body: body ? JSON.stringify(body) : undefined,
  });
  const data = await res.json();
  if (!res.ok) throw Object.assign(new Error(data.error.message), { status: res.status, code: data.error.code, data });
  return data;
}

// Ключ идемпотентности = номер заказа: повтор не купит второй код.
export async function codeForOrder(orderId, slug, minutes) {
  for (let attempt = 1; ; attempt++) {
    try {
      const r = await call("/promocodes", { slug, minutes, quantity: 1, idempotencyKey: `funpay-${orderId}` });
      return `Ваш код: ${r.codes[0].code}\nАктивировать: https://rentplay.games/redeem?code=${r.codes[0].code}`;
    } catch (e) {
      if (e.code === "insufficient_funds") throw new Error(`Пополните баланс: не хватает ${e.data.error.needRub} ₽`);
      if (attempt < 3 && (e.status === 429 || e.status >= 500 || !e.status)) {
        await new Promise((r) => setTimeout(r, 2000 * attempt));
        continue;
      }
      throw e;
    }
  }
}