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). Новая покупка — новый ключ.
Жизнь промокода
- active — куплен, ждёт покупателя. Действует 30 дней. Можно вернуть — деньги вернутся на баланс.
- redeemed — покупатель ввёл код на сайте (
rentplay.games/redeem?code=…) или прислал боту, аренда началась. В полеrental— статус аренды иendsAt. Продлить можно, пока аренда идёт. - refunded — вы вернули код, он больше не работает.
- expired — не активирован за 30 дней. Деньги за истёкший код не возвращаются — возвращайте заранее.
Если в момент ввода все аккаунты с игрой заняты, код не сгорает: покупатель увидит, когда освободится ближайший, и введёт код позже.
Методы
/meБаланс, статус партнёра и скидка, права ключа.
/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}}]}/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, …}, …]}/promocodes?status=&limit=&before=scope: promoВаши коды, новые сверху. status: active | redeemed | refunded | expired; limit до 200. Следующая страница — before= из поля nextBefore ответа (null — дальше пусто).
/promocodes/{code}scope: promoОдин код: статус, когда активирован, идёт ли аренда и до скольких.
/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}/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}/transactions?limit=&before=Журнал вашего баланса: пополнения, покупки кодов, продления, возвраты. Курсор — как у списка кодов.
/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 — для человека.
| 401 | unauthorized | Нет ключа, ключ неверный или отозван |
| 402 | insufficient_funds | Не хватает баланса; в ответе needRub — сколько |
| 403 | forbidden_scope | У ключа нет нужного права (promo или bonus) |
| 403 | not_partner | Партнёрство не подтверждено или приостановлено |
| 404 | not_found | Нет такой игры или кода (чужой код — тоже 404) |
| 409 | not_active | Продление: код не активирован или аренда уже закончилась |
| 409 | not_refundable | Возврат: код уже активирован, истёк или возвращён |
| 422 | validation_error | Неверные поля, срок аренды или битый JSON (invalid_json) |
| 429 | rate_limited | Больше 120 запросов в минуту на ключ; заголовок Retry-After |
| 429 | daily_limit | Бонусы: исчерпан дневной предел ключа |
| 500 | internal | Сбой у нас — повторите запрос с тем же 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;
}
}
}