PayStars Unified API · v3.0
One API.
The same balance as StarKerak.
The website, Telegram WebApp and external API all use the same StarKerak prices, balance and fulfillment engine. API responses are JSON.
Authentication
Create an API key in the Dashboard. The full key is shown only once. Keep it in your server environment; never put it in frontend JavaScript or a Git repository.
X-API-Key: ps_your_secret_api_key Content-Type: application/json Idempotency-Key: order_20260719_0001
Limits, verification and idempotency
- 3 requests per second per API key.
- Recipient check limit via API: 120 per minute per account; 12 for website/WebApp.
- Before buying you must call
/check-usernameand obtain averification_tokenvalid for 180 seconds. - Each verification token is bound to exactly one logical order; retrying safely with the same idempotency key is supported.
- Send a unique 8–128 character
Idempotency-Keywith Stars and Premium purchases. - Re-sending the same
Idempotency-Keywith the same payload does not charge again; the original order is returned. - Reusing an idempotency key with a different payload returns
409 Conflict.
API status
/statuscurl https://paystars.uz/api/v1/status
USD balance and price
The account and dollar balance come from StarKerak. Prices are in USD: every order costs the Fragment price + 5%.
/accountcurl https://paystars.uz/api/v1/account \
-H "X-API-Key: $PAYSTARS_API_KEY"{
"ok": true,
"user": { "telegram_id": 123456789 },
"balances": { "USD": "12.50" },
"currency_pref": "USD"
}/pricesFor a USD price use POST /usd/quote (below): price = Fragment + 5%.
Username check
This endpoint uses Fragment to resolve the name, photo and Premium status. A verification_token is returned only for a recipient that can be purchased for.
/check-usernamecurl -X POST https://paystars.uz/api/v1/check-username \ -H "X-API-Key: $PAYSTARS_API_KEY" \ -H "Content-Type: application/json" \ -d '{"username":"telegram","kind":"premium"}'
{
"valid": true,
"username": "telegram",
"nickname": "Telegram User",
"photo_url": "https://cdn4.telesco.pe/...",
"premium_active": false,
"verification_token": "...",
"verification_expires_in": 180
}kind is stars or premium. If Premium is already active, you get premium_active: true, valid: false and a null token.
Stars order
/stars/buycurl -X POST https://paystars.uz/api/v1/stars/buy \ -H "X-API-Key: $PAYSTARS_API_KEY" \ -H "Idempotency-Key: order_20260719_0001" \ -H "Content-Type: application/json" \ -d '{"username":"telegram","quantity":100,"verification_token":"TOKEN_FROM_CHECK","currency":"USD","max_usd":1.58}'
{
"ok": true,
"order_id": 1842,
"status": "processing",
"quantity": 100,
"cost": 1.58,
"balance": 10.92,
"currency": "USD"
}Dollar (USD) balance and USDT
API payments are made from the dollar balance. It is topped up with USDT on the TON or BEP20 network (minimum 1 USDT). USD price = Fragment price + 5% (rounded up to the cent).
/usd/walletDollar balance, TON address and personal memo, BEP20 address (with QR images).
{
"ok": true,
"usd": { "balance": "12.50", "micros": 12500000 },
"min_deposit": "1.00",
"ton": { "address": "UQ...", "memo": "...", "enabled": true, "ttl_min": 30 },
"bsc": { "address": "0x...", "contract": "0x55d398326f99059fF775485246999027B3197955", "enabled": true, "ttl_min": 30 }
}usd.balance is the dollar balance (string); usd.micros: 1 USD = 1,000,000 micros.
/usd/invoice{"amount": 10, "chain": "ton"} or "chain": "bsc" → an exact amount valid for 30 minutes (e.g. 10.0437). Send exactly that amount of USDT to the address on the chosen network (no memo needed for TON). /usd/bsc-invoice still works as a legacy alias (default: bsc).
/usd/invoice/{id}/cancelCancels a pending invoice (e.g. to change the amount). A cancelled exact amount is not given to anyone else for 24 hours, and a payment arriving in that window is still credited to your balance.
/usd/invoice/{id}Status: pending, paid, expired or cancelled.
/usd/quote{"kind":"stars","username":"durov","quantity":100} or {"kind":"premium","username":"durov","months":3} → {"usd": 1.58}.
To pay from the dollar balance, add "currency": "USD" and "max_usd": 1.58 from /usd/quote to the /stars/buy or /premium/buy payload. If the Fragment price exceeds max_usd, nothing is charged and 409 price_changed is returned — get a new quote. If currency is omitted, the account's saved currency preference (pref in /usd/wallet) is used — so always send "currency": "USD". cost and balance in the response are in the currency given by the currency field.
curl -X POST https://paystars.uz/api/v1/stars/buy \
-H "X-API-Key: $PAYSTARS_API_KEY" -H "Idempotency-Key: stars_8f2c1a9b" \
-H "Content-Type: application/json" \
-d '{"username":"durov","quantity":100,"verification_token":"...","currency":"USD","max_usd":1.58}'Orders
/ordersReturns the latest 50 orders placed via website, WebApp and API.
/order/{order_id}curl https://paystars.uz/api/v1/order/1842 \
-H "X-API-Key: $PAYSTARS_API_KEY"Errors
| Status | Meaning | What to do |
|---|---|---|
| 400 | Invalid payload or idempotency key | Check the request format |
| 401 | Invalid API key | Create a new key |
| 402 | Insufficient USD balance | Top up with USDT |
| 409 | Verification expired/used or idempotency conflict | Re-check the recipient |
| 422 | Username or service unavailable | Check the recipient |
| 402 | insufficient_balance (USD) | Top up the dollar balance with USDT |
| 409 | price_changed — USD price exceeded max_usd | Get a new /usd/quote |
| 429 | Rate limit exceeded | Retry after Retry-After |
| 503 | Service temporarily unavailable | Use exponential backoff |
{
"detail": {
"code": "insufficient_balance",
"message": "StarKerak balance is insufficient"
}
}Working examples
In different programming languages
Each example checks the recipient, obtains the short-lived token and uses an idempotency key so the same order can be retried safely.
API="https://paystars.uz/api/v1"
ORDER_KEY="stars_$(uuidgen)"
VERIFY=$(curl --fail-with-body -sS -X POST "$API/check-username" \
-H "X-API-Key: $PAYSTARS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"username":"telegram","kind":"stars"}')
TOKEN=$(printf '%s' "$VERIFY" | jq -r '.verification_token')
curl --fail-with-body -sS -X POST "$API/stars/buy" \
-H "X-API-Key: $PAYSTARS_API_KEY" \
-H "Idempotency-Key: $ORDER_KEY" \
-H "Content-Type: application/json" \
-d "{\"username\":\"telegram\",\"quantity\":100,\"verification_token\":\"$TOKEN\",\"currency\":\"USD\"}"import { randomUUID } from 'node:crypto';
const base = 'https://paystars.uz/api/v1';
const headers = { 'X-API-Key': process.env.PAYSTARS_API_KEY, 'Content-Type': 'application/json' };
const verify = await fetch(`${base}/check-username`, {
method: 'POST', headers, body: JSON.stringify({ username: 'telegram', kind: 'stars' })
}).then(async r => { if (!r.ok) throw new Error(await r.text()); return r.json(); });
const order = await fetch(`${base}/stars/buy`, {
method: 'POST', headers: { ...headers, 'Idempotency-Key': `stars_${randomUUID()}` },
body: JSON.stringify({ username: verify.username, quantity: 100, verification_token: verify.verification_token, currency: 'USD' })
}).then(async r => { if (!r.ok) throw new Error(await r.text()); return r.json(); });
console.log(order.order_id, order.status);import os, uuid, requests
base = "https://paystars.uz/api/v1"
headers = {"X-API-Key": os.environ["PAYSTARS_API_KEY"]}
verify = requests.post(f"{base}/check-username", headers=headers,
json={"username": "telegram", "kind": "premium"}, timeout=15)
verify.raise_for_status()
recipient = verify.json()
order = requests.post(f"{base}/premium/buy", headers={
**headers, "Idempotency-Key": f"premium_{uuid.uuid4()}"
}, json={"username": recipient["username"], "months": 3,
"verification_token": recipient["verification_token"], "currency": "USD"}, timeout=20)
order.raise_for_status()
print(order.json())<?php
$base = 'https://paystars.uz/api/v1';
$key = getenv('PAYSTARS_API_KEY');
function postJson($url, $headers, $body) {
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true,
CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => json_encode($body), CURLOPT_TIMEOUT => 20]);
$raw = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
if ($status < 200 || $status >= 300) throw new RuntimeException($raw);
return json_decode($raw, true, flags: JSON_THROW_ON_ERROR);
}
$common = ["X-API-Key: $key", 'Content-Type: application/json'];
$user = postJson("$base/check-username", $common, ['username' => 'telegram', 'kind' => 'stars']);
$order = postJson("$base/stars/buy", [...$common, 'Idempotency-Key: stars_'.bin2hex(random_bytes(16))],
['username' => $user['username'], 'quantity' => 100, 'verification_token' => $user['verification_token'], 'currency' => 'USD']);
print_r($order);package main
import ("bytes"; "encoding/json"; "fmt"; "io"; "net/http"; "os"; "github.com/google/uuid")
func post(path string, body any, idempotency string, out any) error {
data, _ := json.Marshal(body)
req, _ := http.NewRequest("POST", "https://paystars.uz/api/v1"+path, bytes.NewReader(data))
req.Header.Set("X-API-Key", os.Getenv("PAYSTARS_API_KEY")); req.Header.Set("Content-Type", "application/json")
if idempotency != "" { req.Header.Set("Idempotency-Key", idempotency) }
res, err := http.DefaultClient.Do(req); if err != nil { return err }; defer res.Body.Close()
if res.StatusCode >= 300 { raw, _ := io.ReadAll(res.Body); return fmt.Errorf("API %d: %s", res.StatusCode, raw) }
return json.NewDecoder(res.Body).Decode(out)
}
func main() {
var user struct { Username string `json:"username"`; VerificationToken string `json:"verification_token"` }
if err := post("/check-username", map[string]any{"username":"telegram", "kind":"stars"}, "", &user); err != nil { panic(err) }
var order map[string]any
if err := post("/stars/buy", map[string]any{"username":user.Username, "quantity":100,
"verification_token":user.VerificationToken, "currency":"USD"}, "stars_"+uuid.NewString(), &order); err != nil { panic(err) }
fmt.Println(order)
}Integrate with a single prompt
AI prompt
Send this prompt to Codex, ChatGPT or another coding agent. Replace the values in brackets to fit your project.
You are a senior backend and application-security engineer. Build me a complete, production-ready integration with the PayStars API.
PROJECT TYPE: [Telegram bot / web backend / mobile app backend]
LANGUAGE AND FRAMEWORK: [e.g. Python 3.12 + aiogram 3 + FastAPI]
PROJECT NAME: [name]
PayStars base URL: https://paystars.uz/api/v1
Read the API key only from the PAYSTARS_API_KEY environment variable. Never expose it to the frontend, logs, Git or error messages. All requests must be HTTPS and server-side. Add timeouts, structured logging (without secrets), input validation and tests.
Implement the following flow completely:
1. Get the USD balance via GET /usd/wallet. Do not hardcode prices: before every purchase get the USD price via POST /usd/quote (Fragment price + 5%).
2. Before a Stars or Premium purchase, send {"username":"...","kind":"stars|premium"} to POST /check-username.
3. Check valid, nickname, photo_url, premium_active and verification_token in the response. Show the user the photo, name, @username and Premium status and get explicit confirmation. Do not create an order without confirmation.
4. verification_token is valid for 180 seconds. If it expires or a 409 is returned, re-check the username.
5. For Stars send username, quantity and verification_token to POST /stars/buy; for Premium send username, months (3, 6 or 12) and verification_token to POST /premium/buy. In every purchase send "currency": "USD" and the quoted price as "max_usd"; on 409 price_changed fetch a new quote, on 402 insufficient_balance ask the user to top up the USD balance with USDT (TON or BEP20).
6. For each logical order generate an 8–128 character Idempotency-Key based on a cryptographic UUID. On network retries reuse exactly the same key and payload; never reuse it for another order.
7. Poll status via GET /order/{order_id} and show history via GET /orders. Handle processing/pending/completed/failed states correctly.
8. The API limit is 3 requests per second per key. On 429 honour Retry-After; on 503 and transient network errors use bounded exponential backoff with jitter. Give the user a safe, clear message for 400, 401, 402, 409, 422, 429 and 503.
9. Do not trust API responses blindly: validate against a schema, normalise usernames, enforce quantity/months limits server-side. Display photo_url only as an allowed HTTPS image. Never log the verification token, API key or full request headers.
10. Handle concurrent clicks by the same user, replay, duplicate orders, forged callbacks, race conditions, timeouts and process restarts. Persist state in the database with transactions and bind the user's confirmation to the order.
If the project is a Telegram bot: create /start, "Buy Stars", "Buy Premium", "Balance", "Orders" menus; collect the username and amount step by step; after the check show inline "Confirm/Cancel" buttons; verify callback ownership and TTL; secure the webhook secret or long-polling setup.
Deliver real, runnable code, not pseudo-code: exact folder structure, dependency file, .env.example (no real secrets), database migrations, Dockerfile, healthcheck, README, local run and deploy instructions, unit/integration tests. Put the API client in a separate module. Treat the PayStars API as the single source of truth for all prices, balances and orders; never connect to the StarKerak database directly. First ask for any missing business values as a short list, and make the remaining technical decisions yourself with safe defaults.