Shiba Bank API v1.0

Credit Stars from your wallet in @barboskich_stars_bot to people's wallets straight from your own service: rewards for actions, prizes, payouts to moderators. The Stars never leave the bot: they move from wallet to wallet, like the +send command.

Base URL: https://shiba-bank.com/api/v1

Quick start

  1. Open @barboskich_stars_bot, send /api and press “🔑 Create key”. The key is shown once: store it in the SHIBA_API_KEY environment variable on your server.
  2. Top up your wallet in the bot: transfers are paid from your balance only.
  3. Check the key and the balance:
    curl https://shiba-bank.com/api/v1/balance -H "Authorization: Bearer $SHIBA_API_KEY"
  4. Send Stars:
    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"}'
    Answer:
    {
      "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"
      }
    }

The bot messages the recipient with the amount, the sender and your comment unless notify is false.

Basics

HeaderWhenValue
AuthorizationalwaysBearer sbk_…
Content-TypePOSTapplication/json
Idempotency-KeyPOST /transfers, optional instead of the fieldsame as idempotency_key

Keys and authentication

Every request except /status carries the key in a header:

Authorization: Bearer sbk_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

X-Api-Key: sbk_… works too. A key in the URL (?key=) is refused and should be treated as exposed.

Security

What a key can do: read your balance, limits and its own transfers; check whether a person can receive Stars; send Stars from your wallet to people's wallets in the bot.

What a key can never do: withdraw Stars from the bot, sell or buy them, charge anybody else's wallet, change its own limits or addresses, see other people's data. Settings change only inside the bot.

How to protect the key

What the server does

Limits

WhatHow muchWhere to change
One transferdefault 500 ⭐, at most 100,000 ⭐/api → 📏
A day (UTC)default 2000 ⭐, at most 1,000,000 ⭐/api → 📅
Requests per key10 per second, bursts up to 30—
Transfers per key3 per second, bursts up to 10; one at a time—
Requests per IP10 per second, bursts up to 40 (beyond that, HTTP 429 from the web server)—
Request body4096 bytes—
Allowed addressesup to 10 IPs or networks/api → 🌐

The daily limit follows the UTC day (it resets at 00:00 UTC) and includes every completed or started transfer of the key. Current figures are in GET /me.

With 429 and 503 read the Retry-After header: that many seconds before a retry.

Idempotency and retries

Networks fail. If the answer to a transfer never arrived, you do not know whether the Stars moved. So every transfer carries your idempotency key, a stable name of the operation: moderation-8841, order-1234-reward, daily-2026-09-23-user-42. Repeat the request with the same key as often as you like: the Stars move once.

SituationWhat the API answers
A new keyMakes the transfer.
Same key, same fields, completedThe same answer, replayed: true, nothing charged again.
Same key, same fields, pendingFinishes that same transfer.
Same key, different fields409 idempotency_conflict.
Same key after a refusal (balance, limit, frozen)Tries again: a refusal moved nothing and did not take the key.

The rule is simple: derive the key from your data, never a fresh random one per attempt. A random key per attempt switches the double-payment protection off.

Safe to repeat: a network error, a timeout, HTTP 429, 500, 503. Do not repeat unchanged: other 4xx answers will not change by repetition.

StatusMeaning
completedThe Stars are with the recipient. Final.
pendingStarted, outcome not recorded yet (a dropped connection or a restart). Repeat the same request with the same key and it finishes.
refusedRefused, nothing moved. The code is in error.

POST /transfers — send Stars

Takes Stars from your wallet and credits them to the recipient's wallet in the bot.

FieldTypeRequiredDescription
telegram_idintegeryesThe recipient's Telegram id. They must have started @barboskich_stars_bot at least once.
amountintegeryesStars to send: a whole number from 1 to 100,000, and within the key's limit.
commentstringnoThe purpose of the payment, up to 255 characters. One line: line breaks and repeated spaces are joined. No links, domains, @mentions or hashtags.
notifybooleanno, trueWhether the bot messages the recipient about the credit.
idempotency_keystringyes*Your unique operation key, 1–64 characters A–Z a–z 0–9 _ - . :. May be sent as the Idempotency-Key header. *Not needed with dry_run.
dry_runbooleanno, falseCheck everything (key, recipient, limits, balance) and move nothing.

Request

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 — 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"
  }
}
FieldDescription
successtrue: the Stars are credited.
end_balanceYour wallet balance right after this transfer.
replayedtrue when this repeats a completed transfer: nothing new was charged.
transfer.idOur transfer id, tr_….
transfer.statuscompleted, pending or refused.
transfer.errorThe refusal code of a refused one, otherwise null.

Checking without sending — dry_run

{
  "telegram_id": 123456789,
  "amount": 5,
  "dry_run": true
}
{
  "success": true,
  "dry_run": true,
  "end_balance": 307,
  "balance": 312
}

Error — 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} — one transfer

A transfer's state by our id (tr_…). Only your account's transfers are visible.

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 — transfer history

Your account's API transfers, newest first.

ParameterDescription
limit1–100, default 20.
cursornext_cursor of the previous answer: the next page.
statuspending | completed | refused
idempotency_keyFind a transfer by your own key: handy when an answer was lost.
telegram_idOnly transfers to this recipient.
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 is null on the last page.

GET /balance — balance

How many Stars your wallet has available right now.

curl https://shiba-bank.com/api/v1/balance -H "Authorization: Bearer $SHIBA_API_KEY"
{
  "success": true,
  "telegram_id": 555000111,
  "balance": 312
}

GET /me — key, limits, balance

Everything about the key in one request: whose it is, the limits, today's use, allowed addresses.

{
  "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 they receive

A check before promising a reward: can_receive is true when the person has started @barboskich_stars_bot and their wallet is available. The API reveals no name or other data.

{
  "success": true,
  "telegram_id": 123456789,
  "can_receive": true
}

If false, ask the person to open @barboskich_stars_bot and press Start: Stars are never sent to an account the bot does not know, or they would be lost.

GET /status — is the API up

No key needed. For monitoring.

{
  "success": true,
  "api_version": "1.0",
  "time": "2026-09-23T14:13:05Z",
  "docs": "/api/docs"
}

An OpenAPI 3.1 description for Postman, Swagger and code generators: /api/v1/openapi.json

Notification and comment

With notify: true the recipient gets a message from the bot in their own language:

⭐ You received 5 ⭐
From: @my_service_owner

💬 «for nickname moderation»
👛 Wallet · 312 Stars

Comment rules

Errors

{
  "success": false,
  "error": {
    "code": "insufficient_balance",
    "message": "Not enough Stars in the wallet.",
    "balance": 3,
    "required": 5
  },
  "request_id": "req_9b1e04c27d5a6f33"
}
CodeHTTPMeaning and what to do
invalid_json400The body is not a UTF-8 JSON object, or a field appears twice.
invalid_request400A field is missing, of the wrong type or out of range; field names it.
unknown_field400A field the API does not know (often a typo such as ammount).
invalid_comment400comment is not a string.
comment_too_long400The comment is longer than 255 characters.
comment_not_allowed400The comment contains a link, a domain, an @mention or a hashtag.
idempotency_key_required400A transfer without an idempotency key.
invalid_idempotency_key400The key has the wrong format, or the header and the body disagree.
key_in_url400The API key was sent in the URL. Treat it as exposed and issue a new one.
unauthorized401The key is missing, wrong or revoked.
key_blocked403API access of this account is blocked by the administration.
account_restricted403The key owner's account is restricted in the bot.
ip_not_allowed403The request's address is not allowed. ip is the address the server saw.
not_found404No such transfer (or it is not yours).
recipient_not_found404The recipient has never started the bot, or their wallet is unavailable.
idempotency_conflict409The key already belongs to a different transfer (recipient, amount, comment or notify differ).
payload_too_large413The body is larger than 4096 bytes.
unsupported_media_type415No Content-Type: application/json header.
recipient_is_self422A transfer to yourself.
per_transfer_limit_exceeded422The amount is above the per-transfer limit (limit).
daily_limit_exceeded422The transfer would exceed the daily limit: limit, used_today, remaining.
insufficient_balance422Not enough Stars: balance and required. Nothing moved; the same key may be repeated after a top-up.
wallet_frozen422The sender's or the recipient's wallet is frozen or under review. Nothing moved.
transfer_refused422The wallet refused the transfer. Nothing moved.
account_too_new422The account cannot send Stars yet (hours_left).
rate_limited429Too many requests. Wait Retry-After seconds.
too_many_failed_attempts429Too many wrong-key requests from your address. Check the key and wait.
internal_error500Our error. Contact support with the request_id.
api_disabled503The API is switched off for now.
busy503The transfer queue is busy. Repeat the same request after Retry-After.
outcome_unknown503The outcome is not known yet. Repeat the same request with the same key: it never pays twice.

Any 4xx answer means this request moved nothing. With 5xx the outcome may be unknown: repeat the same request with the same key.

Code examples

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"];
}

Mass payout

There is no batch method: send transfers one by one, each with its own key (say payout-2026-09-23-<telegram_id>). If the script dies halfway, run it again from the start: completed transfers come back with replayed: true and are not charged twice.

for user_id, stars in winners:
    give_stars(user_id, stars, "приз недели", key=f"weekly-2026-39-{user_id}")

FAQ

Where do the Stars for transfers come from?
Only from your wallet in @barboskich_stars_bot. Top it up in the bot: “👛 Wallet” → “Top up”.
What can the recipient do with the Stars?
Everything any wallet Stars allow: withdraw to their Telegram account, sell, give away in a chat, send as a cheque.
Can a transfer be undone?
No. A completed transfer is final, like +send. Use dry_run to check first.
How do I get a person's Telegram id?
Telegram sends it in every update to your bot: message.from.id. The API does not send by @username: usernames change, ids do not.
How much does the API cost?
Nothing. Transfers between wallets carry no fee.
I got 503 outcome_unknown: did the Stars move?
Repeat the same request with the same key: the final answer comes back, never a double charge. Or look at GET /transfers?idempotency_key=….
Need a webhook for incoming transfers, or to accept payment in Stars?
Tell the bot's support what exactly you need: that decides what the next versions add.

Changelog

1.0 — 23.09.2026. Transfers, balance, limits, history, recipient check, OpenAPI.