Shiba Bank API v1.0
Начисляйте звёзды со своего кошелька в @barboskich_stars_bot на кошельки людей прямо из своего сервиса: награды за действия, призы, выплаты модераторам. Звёзды не покидают бот — они переходят из кошелька в кошелёк, как по команде +send.
Базовый адрес: https://shiba-bank.com/api/v1
Быстрый старт
- Откройте @barboskich_stars_bot, отправьте
/apiи нажмите «🔑 Создать ключ». Ключ покажется один раз — сохраните его в переменную окруженияSHIBA_API_KEYна своём сервере. - Пополните кошелёк бота: переводы идут только из вашего баланса.
- Проверьте ключ и баланс:
curl https://shiba-bank.com/api/v1/balance -H "Authorization: Bearer $SHIBA_API_KEY" - Переведите звёзды:
Ответ:curl -X POST https://shiba-bank.com/api/v1/transfers \ -H "Authorization: Bearer $SHIBA_API_KEY" \ -H "Content-Type: application/json" \ -d '{"telegram_id": 123456789, "amount": 5, "comment": "за модерацию никнейма", "notify": true, "idempotency_key": "moderation-8841"}'{ "success": true, "end_balance": 307, "replayed": false, "transfer": { "id": "tr_5f0c2a9e41b7d3c8a6e1f024", "status": "completed", "telegram_id": 123456789, "amount": 5, "comment": "за модерацию никнейма", "notify": true, "idempotency_key": "moderation-8841", "error": null, "end_balance": 307, "created_at": "2026-09-23T14:13:05.120931Z" } }
Получателю бот сам пришлёт сообщение с суммой, отправителем и вашим комментарием, если notify не false.
Основы
- Только HTTPS. Запросы и ответы — JSON в UTF-8.
- В каждом ответе есть
success:trueилиfalse. Приfalse— объектerrorс постояннымcode(на него и опирайтесь в коде),messageдля человека и подробностями, а такжеrequest_id. - Заголовок
X-Request-Idесть у каждого ответа — укажите его, если пишете в поддержку. - Суммы — целые звёзды. Время — ISO 8601 в UTC, с
Zна конце. - Числа можно передавать и строкой из цифр (
"5"), логические — толькоtrue/false. - Неизвестные поля отвергаются (
unknown_field) — так опечатка не превратится в тихо проигнорированный параметр. - Версия в адресе —
/api/v1. Внутри v1 поля только добавляются, ничего не удаляется и не меняет смысл.
| Заголовок | Когда | Значение |
|---|---|---|
Authorization | всегда | Bearer sbk_… |
Content-Type | POST | application/json |
Idempotency-Key | POST /transfers, по желанию вместо поля | тот же, что idempotency_key |
Ключ и авторизация
Каждый запрос, кроме /status, несёт ключ в заголовке:
Authorization: Bearer sbk_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Можно и X-Api-Key: sbk_…. Ключ в адресе (?key=) отвергается и считается раскрытым.
- Ключ — 256 случайных бит, начинается с
sbk_. Бот хранит только его отпечаток SHA-256 и показывает ключ один раз. - Ключ у аккаунта один. «♻️ Новый ключ» в
/apiсразу отключает старый; «🗑 Отозвать» отключает без замены. Лимиты и адреса при этом сохраняются. - Ключ действует от имени владельца: платит его кошелёк, и отправителем получатель видит его.
Безопасность
Что ключ может: читать ваш баланс, лимиты и свои переводы; проверять, может ли человек получить звёзды; переводить звёзды с вашего кошелька на кошельки людей в боте.
Чего ключ не может никогда: выводить звёзды из бота, продавать их, покупать, списывать с чужих кошельков, менять свои лимиты и адреса, видеть чужие данные. Настройки меняются только в самом боте.
Как защитить ключ
- Держите ключ только на сервере: в переменной окружения или хранилище секретов. Не кладите его в репозиторий, в код бота-клиента, в мини-приложение или сайт — всё, что уходит в браузер или телефон, можно прочитать.
- API отвечает только серверам: заголовков CORS нет, и браузер чужой страницы ваш ключ не использует.
- Поставьте в
/api→ «🌐 Адреса» IP своего сервера — ключ перестанет работать откуда-либо ещё. - Выставьте лимиты под реальную нагрузку: суточный лимит — это наибольшее, что можно потерять при утечке ключа.
- Держите на кошельке столько, сколько нужно на ближайшие выплаты, а не всё.
- Подозреваете утечку — сразу «♻️ Новый ключ» или «🗑 Отозвать». Старый ключ перестаёт работать в ту же секунду.
Что делает сервер
- Каждый перевод идёт той же дорогой, что
+send: антифрод, заморозки и баны действуют так же. - Переводы одного ключа выполняются строго по одному, лимиты считаются атомарно — параллельные запросы не проскочат мимо суточного лимита.
- Адрес, с которого часто приходят неверные ключи, временно получает отказ ещё до проверки ключа.
- Комментарий очищается от невидимых и управляющих символов и не может содержать ссылки — получатель видит его как ваши слова, а не как сообщение бота.
Лимиты
| Что | Сколько | Где меняется |
|---|---|---|
| Один перевод | по умолчанию 500 ⭐ максимум 100 000 ⭐ | /api → 📏 |
| Сутки (UTC) | по умолчанию 2000 ⭐ максимум 1 000 000 ⭐ | /api → 📅 |
| Запросы на ключ | 10 в секунду, всплеск до 30 | — |
| Переводы на ключ | 3 в секунду, всплеск до 10; одновременно — один | — |
| Запросы с одного IP | 10 в секунду, всплеск до 40 (сверх — HTTP 429 от веб-сервера) | — |
| Тело запроса | 4096 байт | — |
| Разрешённые адреса | до 10 IP или подсетей | /api → 🌐 |
Суточный лимит считается по суткам UTC (обнуляется в 00:00 UTC = 03:00 МСК) и включает все переводы ключа, выполненные и начатые. Текущие цифры — в GET /me.
При 429 и 503 смотрите заголовок Retry-After — через столько секунд можно повторить.
Идемпотентность и повторы
Сеть рвётся. Если ответ на перевод не дошёл, вы не знаете, списались ли звёзды. Поэтому у каждого перевода есть ваш ключ идемпотентности — постоянное имя операции: moderation-8841, order-1234-reward, daily-2026-09-23-user-42. Повторяйте запрос с тем же ключом сколько угодно — звёзды спишутся один раз.
| Ситуация | Что ответит API |
|---|---|
| Новый ключ | Выполнит перевод. |
| Тот же ключ и те же поля, перевод выполнен | Тот же ответ, replayed: true, ничего не списано повторно. |
| Тот же ключ и те же поля, перевод «pending» | Доведёт тот же перевод до конца. |
| Тот же ключ, другие поля | 409 idempotency_conflict. |
| Тот же ключ после отказа (не хватило звёзд, лимит, заморозка) | Пробует заново: отказ ничего не списал и ключ не занял. |
Правило простое: ключ — из ваших данных, а не случайный на каждую попытку. Случайный ключ на каждую попытку отключает защиту от двойных выплат.
Повторять безопасно: сетевую ошибку, таймаут, HTTP 429, 500, 503. Не повторять без изменений: остальные 4xx — они не изменятся от повторения.
| Статус | Что значит |
|---|---|
completed | Звёзды у получателя. Окончательно. |
pending | Перевод начат, итог ещё не записан (обрыв связи или перезапуск). Повторите тот же запрос с тем же ключом — он доведёт перевод. |
refused | Отказ, ничего не списано. Код — в error. |
POST /transfers — перевод звёзд
Списывает звёзды с вашего кошелька и зачисляет на кошелёк получателя в боте.
| Поле | Тип | Обязательно | Описание |
|---|---|---|---|
telegram_id | integer | да | Telegram id получателя. Он должен хотя бы раз запустить @barboskich_stars_bot. |
amount | integer | да | Сколько звёзд перевести: целое от 1 до 100 000 и не выше лимита ключа. |
comment | string | нет | Назначение платежа, до 255 символов. Одна строка: переносы и повторные пробелы склеиваются. Без ссылок, доменов, @упоминаний и хештегов. |
notify | boolean | нет, true | Прислать ли получателю сообщение от бота о зачислении. |
idempotency_key | string | да* | Ваш уникальный ключ операции, 1–64 символа A–Z a–z 0–9 _ - . :. Можно передать заголовком Idempotency-Key. *Не нужен при dry_run. |
dry_run | boolean | нет, false | Проверить всё (ключ, получателя, лимиты, баланс) и ничего не переводить. |
Запрос
curl -X POST https://shiba-bank.com/api/v1/transfers \
-H "Authorization: Bearer $SHIBA_API_KEY" \
-H "Content-Type: application/json" \
-d '{"telegram_id": 123456789, "amount": 5, "comment": "за модерацию никнейма", "notify": true, "idempotency_key": "moderation-8841"}'
Успешный ответ — 200
{
"success": true,
"end_balance": 307,
"replayed": false,
"transfer": {
"id": "tr_5f0c2a9e41b7d3c8a6e1f024",
"status": "completed",
"telegram_id": 123456789,
"amount": 5,
"comment": "за модерацию никнейма",
"notify": true,
"idempotency_key": "moderation-8841",
"error": null,
"end_balance": 307,
"created_at": "2026-09-23T14:13:05.120931Z"
}
}
| Поле | Описание |
|---|---|
success | true — звёзды зачислены. |
end_balance | Остаток на вашем кошельке сразу после этого перевода. |
replayed | true, если это повтор уже выполненного перевода: новых списаний не было. |
transfer.id | Наш номер перевода, tr_…. |
transfer.status | completed, pending или refused. |
transfer.error | Код отказа у refused, иначе null. |
Проверка без перевода — dry_run
{
"telegram_id": 123456789,
"amount": 5,
"dry_run": true
}
{
"success": true,
"dry_run": true,
"end_balance": 307,
"balance": 312
}
Ошибка — 422
{
"success": false,
"error": {
"code": "insufficient_balance",
"message": "Not enough Stars in the wallet.",
"balance": 3,
"required": 5
},
"request_id": "req_9b1e04c27d5a6f33"
}
GET /transfers/{id} — один перевод
Состояние перевода по нашему id (tr_…). Видны только переводы вашего аккаунта.
curl https://shiba-bank.com/api/v1/transfers/tr_5f0c2a9e41b7d3c8a6e1f024 -H "Authorization: Bearer $SHIBA_API_KEY"
{
"success": true,
"transfer": {
"id": "tr_5f0c2a9e41b7d3c8a6e1f024",
"status": "completed",
"telegram_id": 123456789,
"amount": 5,
"comment": "за модерацию никнейма",
"notify": true,
"idempotency_key": "moderation-8841",
"error": null,
"end_balance": 307,
"created_at": "2026-09-23T14:13:05.120931Z"
}
}
GET /transfers — история переводов
Переводы через API вашего аккаунта, новые первыми.
| Параметр | Описание |
|---|---|
limit | 1–100, по умолчанию 20. |
cursor | next_cursor из прошлого ответа — следующая страница. |
status | pending | completed | refused |
idempotency_key | Найти перевод по своему ключу — удобно, если ответ потерялся. |
telegram_id | Только переводы этому получателю. |
curl "https://shiba-bank.com/api/v1/transfers?limit=50&status=completed" -H "Authorization: Bearer $SHIBA_API_KEY"
{
"success": true,
"transfers": [
{
"id": "tr_5f0c2a9e41b7d3c8a6e1f024",
"status": "completed",
"telegram_id": 123456789,
"amount": 5,
"comment": "за модерацию никнейма",
"notify": true,
"idempotency_key": "moderation-8841",
"error": null,
"end_balance": 307,
"created_at": "2026-09-23T14:13:05.120931Z"
}
],
"next_cursor": "tr_5f0c2a9e41b7d3c8a6e1f024"
}
next_cursor равен null на последней странице.
GET /balance — баланс
Сколько звёзд на вашем кошельке доступно прямо сейчас.
curl https://shiba-bank.com/api/v1/balance -H "Authorization: Bearer $SHIBA_API_KEY"
{
"success": true,
"telegram_id": 555000111,
"balance": 312
}
GET /me — ключ, лимиты, баланс
Всё о ключе одним запросом: чей он, лимиты, сколько потрачено сегодня, разрешённые адреса.
{
"success": true,
"telegram_id": 555000111,
"username": "my_service_owner",
"balance": 312,
"key": {
"prefix": "sbk_Q2x9aZ1p…",
"created_at": "2026-09-23T10:00:00Z",
"last_used_at": "2026-09-23T14:12:44Z"
},
"limits": {
"per_transfer": 500,
"daily": 2000,
"used_today": 120,
"remaining_today": 1880,
"resets_at": "2026-09-24T00:00:00Z",
"allowed_ips": [
"203.0.113.7"
]
},
"api_version": "1.0"
}
GET /recipients/{telegram_id} — можно ли перевести
Проверка до обещания награды: can_receive — true, если человек запускал @barboskich_stars_bot и его кошелёк доступен. Имени и других данных API не раскрывает.
{
"success": true,
"telegram_id": 123456789,
"can_receive": true
}
Если false — попросите человека открыть @barboskich_stars_bot и нажать «Старт»: звёзды на аккаунт, который бот не знает, не переводятся, иначе они бы потерялись.
GET /status — жив ли API
Без ключа. Для мониторинга.
{
"success": true,
"api_version": "1.0",
"time": "2026-09-23T14:13:05Z",
"docs": "/api/docs"
}
Описание OpenAPI 3.1 для Postman, Swagger и генераторов кода: /api/v1/openapi.json
Уведомление и комментарий
При notify: true получатель получает от бота сообщение на своём языке:
От: @my_service_owner
💬 «за модерацию никнейма»
👛 Кошелёк · 312 Stars
- Отправитель — владелец ключа: @username, а без него — имя и id.
- Сообщения идут через очередь бота: обычно за секунды, при массовых выплатах — дольше, но доходят. Звёзды зачислены в момент ответа API, сообщение их не ждёт и на них не влияет.
- Если человек заблокировал бота, сообщения не будет, а звёзды всё равно на его кошельке.
notify: false— тихое зачисление: звёзды появятся в кошельке без сообщения.
Правила комментария
- До 255 символов после очистки; эмодзи можно.
- Одна строка: переносы и лишние пробелы склеиваются в один пробел; невидимые и управляющие символы удаляются.
- Нельзя: ссылки, домены (
site.com),t.me/…, @упоминания, хештеги — ответcomment_not_allowed. Так сообщение бота нельзя превратить в рекламу или фишинг. - Комментарий хранится с переводом и возвращается в
transfer.comment.
Ошибки
{
"success": false,
"error": {
"code": "insufficient_balance",
"message": "Not enough Stars in the wallet.",
"balance": 3,
"required": 5
},
"request_id": "req_9b1e04c27d5a6f33"
}
| Код | HTTP | Что значит и что делать |
|---|---|---|
invalid_json | 400 | Тело не JSON-объект в UTF-8 или поле повторено дважды. |
invalid_request | 400 | Поле отсутствует, не того типа или вне диапазона — какое, скажет field. |
unknown_field | 400 | Поле, которого API не знает (часто опечатка: ammount). |
invalid_comment | 400 | comment не строка. |
comment_too_long | 400 | Комментарий длиннее 255 символов. |
comment_not_allowed | 400 | В комментарии ссылка, домен, @упоминание или хештег. |
idempotency_key_required | 400 | Перевод без ключа идемпотентности. |
invalid_idempotency_key | 400 | Ключ не по формату или в заголовке и в теле разные ключи. |
key_in_url | 400 | Ключ API передан в адресе. Считайте его раскрытым и выпустите новый. |
unauthorized | 401 | Ключа нет, он неверный или отозван. |
key_blocked | 403 | Доступ к API у аккаунта заблокирован администрацией. |
account_restricted | 403 | Аккаунт владельца ключа ограничен в боте. |
ip_not_allowed | 403 | Адрес запроса не в списке разрешённых. Поле ip — какой адрес видит сервер. |
not_found | 404 | Нет такого перевода (или он чужой). |
recipient_not_found | 404 | Получатель ни разу не запускал бота или его кошелёк недоступен. |
idempotency_conflict | 409 | Ключ уже занят другим переводом (другие получатель, сумма, комментарий или notify). |
payload_too_large | 413 | Тело больше 4096 байт. |
unsupported_media_type | 415 | Нет заголовка Content-Type: application/json. |
recipient_is_self | 422 | Перевод самому себе. |
per_transfer_limit_exceeded | 422 | Сумма выше лимита на один перевод (limit). |
daily_limit_exceeded | 422 | Перевод превысит суточный лимит: limit, used_today, remaining. |
insufficient_balance | 422 | Не хватает звёзд: balance и required. Ничего не списано, тот же ключ можно повторить после пополнения. |
wallet_frozen | 422 | Кошелёк отправителя или получателя заморожен или на проверке. Ничего не списано. |
transfer_refused | 422 | Кошелёк отказал в переводе. Ничего не списано. |
account_too_new | 422 | Аккаунт пока не может передавать звёзды (hours_left). |
rate_limited | 429 | Слишком часто. Подождите Retry-After секунд. |
too_many_failed_attempts | 429 | С вашего адреса много запросов с неверным ключом. Проверьте ключ и подождите. |
internal_error | 500 | Ошибка у нас. Напишите в поддержку с request_id. |
api_disabled | 503 | API временно выключен. |
busy | 503 | Очередь переводов занята. Повторите тот же запрос через Retry-After. |
outcome_unknown | 503 | Итог пока неизвестен. Повторите тот же запрос с тем же ключом — двойного списания не будет. |
Любой ответ 4xx означает: этим запросом ничего не списано. При 5xx итог может быть неизвестен — повторите тот же запрос с тем же ключом.
Примеры кода
Python (requests)
import os
import time
import requests
API = "https://shiba-bank.com/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['SHIBA_API_KEY']}"}
class ShibaApiError(Exception):
def __init__(self, code: str, message: str, details: dict):
super().__init__(f"{code}: {message}")
self.code, self.details = code, details
def give_stars(telegram_id: int, amount: int, comment: str, key: str, notify: bool = True) -> int:
"""Credit Stars and return the balance left. Safe to call again with the same key."""
payload = {
"telegram_id": telegram_id,
"amount": amount,
"comment": comment,
"notify": notify,
"idempotency_key": key,
}
for attempt in range(6):
try:
response = requests.post(f"{API}/transfers", headers=HEADERS, json=payload, timeout=40)
except requests.RequestException:
time.sleep(2 ** attempt) # the network failed: the same key is safe to repeat
continue
if response.status_code in (429, 503) or response.status_code >= 500:
time.sleep(int(response.headers.get("Retry-After", 2 ** attempt)))
continue
data = response.json()
if data["success"]:
return data["end_balance"]
error = data["error"]
raise ShibaApiError(error["code"], error["message"], error)
raise RuntimeError("the API did not answer; repeat later with the same key")
balance = give_stars(123456789, 5, "за модерацию никнейма", key="moderation-8841")
print("left:", balance)
Node.js
// Node.js 18+ (global fetch)
const API = "https://shiba-bank.com/api/v1";
async function giveStars(telegramId, amount, comment, key, notify = true) {
const body = JSON.stringify({
telegram_id: telegramId, amount, comment, notify, idempotency_key: key,
});
for (let attempt = 0; attempt < 6; attempt++) {
let res;
try {
res = await fetch(`${API}/transfers`, {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SHIBA_API_KEY}`,
"Content-Type": "application/json",
},
body,
});
} catch (e) {
await new Promise(r => setTimeout(r, 1000 * 2 ** attempt));
continue;
}
if (res.status === 429 || res.status >= 500) {
const wait = Number(res.headers.get("Retry-After") || 2 ** attempt);
await new Promise(r => setTimeout(r, 1000 * wait));
continue;
}
const data = await res.json();
if (data.success) return data.end_balance;
const err = new Error(`${data.error.code}: ${data.error.message}`);
err.details = data.error;
throw err;
}
throw new Error("the API did not answer; repeat later with the same key");
}
giveStars(123456789, 5, "за модерацию никнейма", "moderation-8841").then(console.log);
PHP
<?php
function give_stars(int $telegramId, int $amount, string $comment, string $key, bool $notify = true): int {
$ch = curl_init("https://shiba-bank.com/api/v1/transfers");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 40,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("SHIBA_API_KEY"),
"Content-Type: application/json",
],
CURLOPT_POSTFIELDS => json_encode([
"telegram_id" => $telegramId,
"amount" => $amount,
"comment" => $comment,
"notify" => $notify,
"idempotency_key" => $key,
], JSON_UNESCAPED_UNICODE),
]);
$raw = curl_exec($ch);
curl_close($ch);
if ($raw === false) {
throw new RuntimeException("network error: repeat with the same key");
}
$data = json_decode($raw, true);
if (!$data["success"]) {
throw new RuntimeException($data["error"]["code"] . ": " . $data["error"]["message"]);
}
return $data["end_balance"];
}
Массовая выплата
Отдельного пакетного метода нет: отправляйте переводы по одному, каждый со своим ключом (например, payout-2026-09-23-<telegram_id>). Если скрипт упал на середине — запустите его заново целиком: уже выполненные переводы вернутся с replayed: true и не спишутся второй раз.
for user_id, stars in winners:
give_stars(user_id, stars, "приз недели", key=f"weekly-2026-39-{user_id}")
FAQ
- Откуда берутся звёзды для переводов?
- Только с вашего кошелька в @barboskich_stars_bot. Пополнить — в боте: «👛 Кошелёк» → «Пополнить».
- Что может получатель со звёздами?
- Всё, что с любыми звёздами кошелька: вывести на аккаунт Telegram, продать, раздать в чате, отправить чеком.
- Можно ли отменить перевод?
- Нет. Выполненный перевод окончателен, как
+send. Для проверки используйтеdry_run. - Как узнать Telegram id человека?
- Его присылает Telegram в каждом апдейте вашего бота:
message.from.id. По @username API не переводит — username меняется, id нет. - Сколько стоит API?
- Бесплатно. Переводы между кошельками без комиссии.
- Мне пришёл 503 outcome_unknown — звёзды ушли?
- Повторите тот же запрос с тем же ключом: придёт окончательный ответ, двойного списания не будет. Или посмотрите
GET /transfers?idempotency_key=…. - Нужен вебхук о входящих переводах или приём оплаты звёздами?
- Напишите в поддержку бота, что именно нужно: так мы решим, что добавить в следующих версиях.
История версий
1.0 — 23.09.2026. Переводы, баланс, лимиты, история, проверка получателя, OpenAPI.