Menu
Getting started

Check balance

Query the quota of an API key or the balance of your account wallet.

There are two kinds of balance:

API key quota Account wallet balance
What it shows Quota of a single API key; for an unlimited key, the account wallet Balance of the whole account, shared by all keys
Endpoint GET /v1/dashboard/billing/subscription + /usage GET /api/user/self
Credential API key sk-xxxx Access token + user ID
Unit USD Quota units (convert to USD)
Shown by third-party clients Yes No — call it from your own code

API key quota

Two OpenAI-compatible billing endpoints return the total quota and the used amount of the API key you call them with. Subtract one from the other to get the remaining balance. Clients that already show an OpenAI balance (for example ChatGPT-Next-Web or LobeChat) work without extra setup — just set the API address to https://rutoken.ai.

Endpoint Returns
GET /v1/dashboard/billing/subscription Total quota of the key (used + remaining)
GET /v1/dashboard/billing/usage Amount already used by the key

Both endpoints are also available without the /v1 prefix. All amounts are in US dollars.

Authentication

Authorization: Bearer sk-xxxx

The result refers to the key in this header. For a key with a quota limit it is that key's own quota. For a key with unlimited quota, which spends directly from the account wallet, it is the account wallet balance — see the note below.

Total quota

GET /v1/dashboard/billing/subscription

bash
curl https://rutoken.ai/v1/dashboard/billing/subscription \
  -H "Authorization: Bearer $API_KEY"
json
{
  "object": "billing_subscription",
  "has_payment_method": true,
  "soft_limit_usd": 120.5,
  "hard_limit_usd": 120.5,
  "system_hard_limit_usd": 120.5,
  "access_until": 0
}
Field Type Description
hard_limit_usd number Total quota of the key in USD (used + remaining). soft_limit_usd and system_hard_limit_usd carry the same value.
access_until integer Key expiry time as a Unix timestamp in seconds. 0 means the key never expires.
has_payment_method boolean Always true; kept for OpenAI compatibility.

For a key with unlimited quota, hard_limit_usd is the account wallet balance plus the amount this key has already used. The usual formula hard_limit_usd − total_usage / 100 therefore gives the wallet balance in USD. When the wallet is empty the result is 0.

This lets a third-party service check the wallet with just an unlimited API key, without an access token.

Used amount

GET /v1/dashboard/billing/usage

bash
curl https://rutoken.ai/v1/dashboard/billing/usage \
  -H "Authorization: Bearer $API_KEY"
json
{
  "object": "list",
  "total_usage": 2050
}
Field Type Description
total_usage number Total amount used by the key, in US cents (USD × 100), as in the OpenAI API. Divide by 100 to get dollars.

total_usage is always the cumulative usage since the key was created. Query parameters such as start_date and end_date are accepted but ignored.

Calculate the balance

text
balance = hard_limit_usd − total_usage / 100
        = 120.5 − 2050 / 100
        = 100.0 USD
cURL
total=$(curl -s https://rutoken.ai/v1/dashboard/billing/subscription \
  -H "Authorization: Bearer $API_KEY" | jq '.hard_limit_usd')
used=$(curl -s https://rutoken.ai/v1/dashboard/billing/usage \
  -H "Authorization: Bearer $API_KEY" | jq '.total_usage / 100')
echo "balance: $(echo "$total - $used" | bc) USD"
Python
import requests

BASE_URL = "https://rutoken.ai"
headers = {"Authorization": "Bearer sk-xxxx"}

sub = requests.get(f"{BASE_URL}/v1/dashboard/billing/subscription", headers=headers).json()
usage = requests.get(f"{BASE_URL}/v1/dashboard/billing/usage", headers=headers).json()
if "error" in sub or "error" in usage:
    raise RuntimeError(sub.get("error") or usage.get("error"))

total = sub["hard_limit_usd"]
used = usage["total_usage"] / 100
print(f"total: {total:.2f} USD, used: {used:.2f} USD, balance: {total - used:.2f} USD")
Node.js
const BASE_URL = "https://rutoken.ai";
const headers = { Authorization: "Bearer sk-xxxx" };

const sub = await (await fetch(`${BASE_URL}/v1/dashboard/billing/subscription`, { headers })).json();
const usage = await (await fetch(`${BASE_URL}/v1/dashboard/billing/usage`, { headers })).json();
if (sub.error || usage.error) throw new Error((sub.error || usage.error).message);

const total = sub.hard_limit_usd;
const used = usage.total_usage / 100;
console.log(`total: ${total} USD, used: ${used} USD, balance: ${total - used} USD`);

Errors

A missing, invalid, disabled or expired key — and a key whose quota is used up — is rejected with HTTP 401 and the standard error format. In other words, a limited key with no balance left gets 401 instead of a balance of 0. An unlimited key is not affected: with an empty wallet it returns a balance of 0.

json
{
  "error": {
    "message": "Invalid token (request id: 20261027123456789abcdef)",
    "type": "new_api_error",
    "code": ""
  }
}

If the balance cannot be read after authentication succeeds, these endpoints still answer with HTTP 200 and an error object instead of the fields above. Always check for an error field before reading hard_limit_usd or total_usage.

Account wallet balance

GET /api/user/self returns the balance of your account wallet — the funds shared by all your API keys. It is authenticated with a personal access token instead of an API key and is meant for your own scripts and back-end services. To give a third-party service read access to the wallet balance, use an unlimited API key with the endpoints above instead.

Get an access token

  1. Sign in to RuToken and open Profile.
  2. In the Security card, open Access Token, click Regenerate and copy the token.
  3. Note your User ID, shown at the top of the Profile page.

The access token grants full access to your account — the same as being signed in — not only balance reads. Keep it on your server; never put it in front-end code or share it. Regenerating the token immediately invalidates the previous one.

Request

Header Value
Authorization Bearer <access token>
New-Api-User Your user ID. Must match the owner of the access token.
cURL
curl https://rutoken.ai/api/user/self \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "New-Api-User: 12"
Python
import requests

BASE_URL = "https://rutoken.ai"
headers = {"Authorization": "Bearer your-access-token", "New-Api-User": "12"}

resp = requests.get(f"{BASE_URL}/api/user/self", headers=headers).json()
if not resp["success"]:
    raise RuntimeError(resp["message"])

quota_per_unit = requests.get(f"{BASE_URL}/api/status").json()["data"]["quota_per_unit"]
print(f"wallet balance: {resp['data']['quota'] / quota_per_unit:.2f} USD")
Node.js
const BASE_URL = "https://rutoken.ai";
const headers = { Authorization: "Bearer your-access-token", "New-Api-User": "12" };

const resp = await (await fetch(`${BASE_URL}/api/user/self`, { headers })).json();
if (!resp.success) throw new Error(resp.message);

const status = await (await fetch(`${BASE_URL}/api/status`)).json();
console.log(`wallet balance: ${(resp.data.quota / status.data.quota_per_unit).toFixed(2)} USD`);

This endpoint does not allow cross-origin (CORS) requests, so call it from a server, not from browser code on another site.

Response

The response contains more profile fields; only the balance-related ones are shown:

json
{
  "success": true,
  "message": "",
  "data": {
    "id": 12,
    "username": "alice",
    "quota": 50000000,
    "used_quota": 1000000,
    "aff_quota": 0
  }
}
Field Type Description
data.quota integer Remaining wallet balance, in quota units.
data.used_quota integer Total amount spent, in quota units.
data.aff_quota integer Referral rewards not yet transferred to the wallet. Not included in quota.

Convert to USD

Amounts are in internal quota units. Divide by quota_per_unit, which the public endpoint GET /api/status returns in data.quota_per_unit (default 500000):

text
USD = quota / quota_per_unit
    = 50000000 / 500000
    = 100.00 USD

Errors

This endpoint uses the {"success": false, "message": "..."} format instead of the OpenAI error format, and some errors come with HTTP 200. Always check success before reading data. The message text may be translated.

Problem HTTP status message
Authorization invalid or regenerated 200 Unauthorized, invalid access token
Authorization missing 401 Unauthorized, not logged in and no access token provided
New-Api-User missing 401 Unauthorized, New-Api-User header not provided
New-Api-User does not match the token owner 401 Unauthorized, New-Api-User does not match logged in user