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
curl https://rutoken.ai/v1/dashboard/billing/subscription \
-H "Authorization: Bearer $API_KEY"
{
"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
curl https://rutoken.ai/v1/dashboard/billing/usage \
-H "Authorization: Bearer $API_KEY"
{
"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
balance = hard_limit_usd − total_usage / 100
= 120.5 − 2050 / 100
= 100.0 USD
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"
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")
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.
{
"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
- Sign in to RuToken and open Profile.
- In the Security card, open Access Token, click Regenerate and copy the token.
- 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 https://rutoken.ai/api/user/self \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "New-Api-User: 12"
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")
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:
{
"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):
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 |