
Build trading bots that place price predictions programmatically. All amounts are in USDT.
Authorization: Bearer cpx_your_api_key_hereKeys grant access to your account only (balance, your bets, placing bets). They cannot withdraw, deposit, or access other users. Keep your key secret; revoke it anytime in your Profile.
/api/v1/leaderboard-bets (no key needed): 900 usernames / minute per IP — batching with usernames= doesn't raise this, it just means fewer round trips/api/v1/symbols (no key needed): 60 requests / minute per IP — the list is cached for 5 minutes, so polling it faster than that returns the same payloadExceeding a limit returns 429 with a Retry-After header (in seconds). The rounds endpoint is cached for ~2s, so polling it frequently is fine.
Base URL: https://cryprex.com
/api/v1/leaderboard-betsPublic, no API key required — recent bets of any user(s) by username. Built for copy-trading bots that need to detect when a leader places a new bet.
Query: username (one user) or usernames (comma-separated, up to 100 — batch instead of one request per user), since (ISO timestamp, optional), limit (per-user, 1–100, default 50).
since is a cursor, not a general date filter — pass it for polling. Without it, results start from the oldest bet still inside the retention window (ascending order), not the user's most recent activity. To poll for new bets: store the createdAt of the last bet you processed, then pass it back as since on the next call to get only what happened after it.
Retention: only the last 40 days of bets are returned. A since older than that is clamped to the window start. This has no effect on copy-trading, which polls the tail.
Rate limit is counted in usernames requested, not HTTP requests — a batch of 20 costs the same as 20 separate calls, it just costs one round trip. With usernames, the response is a flat list with a username field on each bet; with a single username, the shape matches the other endpoints (top-level username, no per-bet field).
/api/v1/accountYour account: id, username, balance, statusText, statusLocked.
/api/v1/account/statusSet your status text — shown as a speech bubble next to your username on the leaderboard. Body: { "statusText": "..." } (max 34 chars, empty/omitted clears it). Fails with 403 if an admin has locked your status.
/api/v1/symbolsPublic, no API key required — every pair you can bet on. Use it to validate a symbol before calling /api/v1/bet.
Query (optional): exchange (futures|spot, default futures).
Returns { symbol, pair } per entry, sorted alphabetically — no prices, no volumes. Live quotes belong to the round you are betting on, not to this list.
/api/v1/bet only checks a symbol against the pattern ^[A-Z0-9]{2,20}USDT$. A well-formed but non-existent pair is not rejected as unknown — it fails later with 503 when no opening price can be fetched. Checking against this list avoids that.
Cached for 5 minutes. If Binance is unreachable the last good list is served with stale: true rather than an empty array.
/api/v1/roundsCurrent rounds and period info (start/end, betting deadline, whether betting is open).
Query (optional): period (5m|15m|1h|1d|1w|1m|1y), exchange (futures|spot, default futures), symbol (e.g. BTCUSDT).
/api/v1/betsYour bets, newest first.
Query (optional): status (active|won|lost|cancelled), limit (1–200, default 50), offset.
/api/v1/betPlace a prediction. Body (JSON):
{
"symbol": "BTCUSDT",
"period": "1h",
"direction": "UP",
"amount": 5,
"exchange": "futures"
}direction: UP or DOWN. amount: min 1 USDT. exchange optional (default futures). The start price, round and betting deadline are determined by the server — you cannot bet after a round's deadline.
Supported symbols are not a fixed whitelist: any USDT pair Binance currently lists as trading works — around 695 on futures and 480 on spot (BTCUSDT, ETHUSDT, BNBUSDT, SOLUSDT, XRPUSDT and so on). Call /api/v1/symbols for the exact live list.
Don't use /api/v1/rounds for this — it returns rounds that already exist, and a round is only created by the first bet on that pair, so at any moment most supported symbols are simply absent from it. You can bet on a pair that has no round yet; the round is opened by your bet.
You predict whether a coin price will be UP or DOWN at the end of a period (1 hour, day, week, month, year). Each period has a betting window; after the deadline no new bets are accepted, but the round runs until it ends. Winners split the losing pool proportionally (a 20% commission applies to the losing pool). /api/v1/bets?status=won
import requests
BASE = "https://cryprex.com"
API_KEY = "cpx_your_api_key_here"
headers = {"Authorization": f"Bearer {API_KEY}"}
# 1. Check balance
acc = requests.get(f"{BASE}/api/v1/account", headers=headers).json()
print("Balance:", acc["account"]["balance"])
# 2. Look at the 1h round for BTC and bet if betting is open
r = requests.get(f"{BASE}/api/v1/rounds",
params={"period": "1h", "symbol": "BTCUSDT"},
headers=headers).json()
can_bet = r["periods"][0]["canBet"]
if can_bet:
resp = requests.post(f"{BASE}/api/v1/bet", headers=headers, json={
"symbol": "BTCUSDT",
"period": "1h",
"direction": "UP",
"amount": 5,
}).json()
print("Bet result:", resp)
else:
print("Betting window for this round is closed")
# 3. Review recent bets
bets = requests.get(f"{BASE}/api/v1/bets",
params={"limit": 5}, headers=headers).json()
for b in bets["bets"]:
print(b["symbol"], b["direction"], b["amount"], b["status"])const BASE = "https://cryprex.com";
const API_KEY = "cpx_your_api_key_here";
const headers = {
"Authorization": `Bearer ${API_KEY}`,
"Content-Type": "application/json",
};
async function main() {
// 1. Balance
const acc = await fetch(`${BASE}/api/v1/account`, { headers }).then(r => r.json());
console.log("Balance:", acc.account.balance);
// 2. Check the 1h BTC round
const r = await fetch(`${BASE}/api/v1/rounds?period=1h&symbol=BTCUSDT`, { headers })
.then(r => r.json());
if (r.periods[0].canBet) {
const resp = await fetch(`${BASE}/api/v1/bet`, {
method: "POST",
headers,
body: JSON.stringify({
symbol: "BTCUSDT",
period: "1h",
direction: "UP",
amount: 5,
}),
}).then(r => r.json());
console.log("Bet result:", resp);
} else {
console.log("Betting window is closed");
}
}
main();Every response is JSON with a success boolean. On error, error holds a message.
401 — invalid or missing API key400 — bad parameters (see error message)429 — rate limit exceeded (retry after header)503 — start price temporarily unavailable, retryPOST /api/v1/bet also returns a stable code next to the message: BETTING_CLOSED, INSUFFICIENT_FUNDS, MIN_AMOUNT, PRICE_UNAVAILABLE, ACCOUNT_BANNED, INVALID_PERIOD, INVALID_DIRECTION, INVALID_EXCHANGE, INVALID_AMOUNT, INVALID_INPUT, USER_NOT_FOUND, BET_FAILED. Branch on code, not on the message text — wording may change, codes will not.
Everything above uses a user API key: it acts as one account and can place bets for it. Publishing your own bot on CRYPREX is a separate track with a separate credential — a bot secret — and the two are not interchangeable.
| User API key | Bot secret |
|---|---|
| Issued in your profile, by yourself | Issued by CRYPREX after your bot is approved |
Authorization: Bearer cpx_… | Authorization: Bearer bot_… |
| Acts as one user account | Acts as your bot: its own wallet only, never someone else's account |
| 120 reads/min, 10 bets/min per key | 60 reads/min, 20 writes/min per bot |
| Endpoints above | /api/v1/bot/* |
On your own server. We do not host your code and there is no iframe. cryprex.com/bots/<your-slug>/* is reverse-proxied to the origin URL you give us, so your bot lives under our domain while running on your infrastructure.
/bots/<slug>/page, not /page. Set that prefix as your framework's basePath (Next.js basePath) so your own links and fetches line up.x-forwarded-host and x-forwarded-proto.502. Suspended or unconfigured bot → our own page for that slug, not a proxy.Origin requirements. An absolute http:// or https:// URL; use HTTPS in production. You may point it at anything reachable from the internet while developing, including a tunnel to your laptop. The origin can be changed later — ask support; the proxy picks it up within 15 seconds, no redeploy on either side. We do not health-check your origin and we do not require you to allowlist us, though you may: all traffic reaches you from our server.
Verifying traffic came through us. Anyone who learns your origin URL can call it directly, bypassing the proxy and your billing. Every proxied request carries x-cryprex-proxy, set to the SHA-256 of your bot secret. Compare it against sha256(your_secret) and reject requests without it. We overwrite any incoming header of that name, so it cannot be forged from outside. Sending the hash rather than the secret is deliberate — the hash is useless for authenticating against our API.
You never touch a user's balance directly. You create an intent, the user confirms it on CRYPREX, you verify the result server-side.
POST /api/v1/bot/charge-intent with { cryprexUserId, amount, description, planDays } → returns intentId, confirmUrl, expiresAt (15 minutes).planDays is the term being sold, in whole days — send it for every dated plan, omit it for lifetime. We do not track subscriptions, so it is the only way we can work out a buyer's unused days if your bot is ever suspended. Without it they cannot be compensated.confirmUrl. Whoever is logged in there pays — cryprexUserId is a UI hint, not the source of truth.GET /api/v1/bot/charge/<intentId> → pending | completed | expired, plus payerUserId. Always confirm here; never trust redirect parameters in the browser, they are trivially forged.POST /api/v1/bot/payout with { toUsername, amount, reason } moves money out of that wallet — to yourself or to a user, e.g. a refund. It spends only what your bot earned.Pass recurring: true to charge-intent (alongside planDays — a lifetime plan has nothing to renew). That does not create a mandate; it puts a checkbox on the checkout page, unticked by default. The mandate exists only if the user ticks it, because from then on the charge happens without them.
planDays days, on the ladder prices — auto-renewal is not a separate plan.GET /api/v1/bot/subscriptions lists yours (filter by cryprexUserId or status). pastDue: true means the last charge failed and the grace period is running.DELETE /api/v1/bot/subscriptions/:id cancels. Calling it twice is fine — a cancelled mandate answers success rather than an error.Cancelling stops future charges only. The period already paid for runs to its end and is not refunded — see the sales-are-final rule below.
/bots, registering the slug and origin, wiring up billing, and issuing the secret. Charged before the bot goes live and not refundable once the secret is issued.charge-intent.Your half is released in 30-day instalments across the term the buyer paid for, not all at once: a 30-day plan pays out once after 30 days, a 90-day plan in three parts, a year in twelve. Instalments are whole cents, with the last one carrying the remainder — a $365 plan releases eleven times at $15.20 and once at $15.30. Released money appears in your bot wallet; you can see what is still locked in your CRYPREX profile. This exists so that a suspension has a cost: it is what buyers are compensated from when a bot is pulled mid-term.I use <botname> as their CRYPREX status. It is earned by keeping the status for 30 continuous days and applies to the next purchase. Offering it is your choice; its terms are not — 20% and 30 days, so the same offer means the same thing on every bot.The plan ladder, identical for every partner:
| Plan | Price | Your half |
|---|---|---|
| 30 days | $50 | $25 |
| 90 days | $120 | $60 |
| 180 days | $210 | $105 |
| 365 days | $365 | $182.50 |
| Lifetime | $500 | $250 |
Every plan is a one-time payment, no recurring charges. You may offer fewer plans than this, but not different prices.
All sales are final. Plans cannot be paused, cancelled or refunded — not by the user, not by you, not partway through. State this plainly on your own pages before checkout so buyers are not surprised by it afterwards. Auto-renewal is the one thing that can be stopped, and stopping it only prevents the next charge: the period already paid for still runs to its end.
All money moves through CRYPREX balances. Taking payment for a plan outside the platform is a breach of these terms.
/bots/apply. You can apply with any balance — nothing is checked at this step. We answer within 72 hours./bots yet./bots happens last, after the bot is finished and we have tested it.Working and listed are two separate states. From the moment your bot is created it is fully working: the key authenticates, and cryprex.com/bots/<your-slug> serves your app. It is simply not in the catalogue, and the link is published nowhere — you use it to build against the real address, we use it to review. Listing only makes it visible to users; nothing about how it runs changes. Being unlisted is not a penalty and never affects your key.
There is no sandbox. Billing is exercised against real balances, so debug it with small amounts — charge-intent accepts any amount above zero, and $1 walks the whole path. Because the secret is issued before listing, you can do all of this without anything being visible to users.
A suspended bot stops authenticating immediately and its slug stops proxying. Suspension is for breach of these terms, not something a user can trigger.
If we suspend or remove your bot, its buyers are made whole. Users holding a plan that has not run out are credited the unused days back to their CRYPREX balance, pro rata. That is CRYPREX honouring the purchase on your behalf — it is not a refund the buyer can ask for, and it does not change the rule that sales are final. Keep this in view when weighing whether a shortcut is worth it: the cost of a suspension lands on you.
The API is provided as-is for building bots on CRYPREX. We may adjust limits to keep the platform stable. Abuse or attempts to overload the service will result in key revocation.