Quick Start
- Create an account — sign up at
/registerand log in. - Subscribe — on the Sim page, purchase the API subscription. You'll receive your API key once on
/sim/welcome. - Authenticate — include your key in every request:
curl -H "Authorization: Bearer sim_your_key_here" \ https://www.childrenoftitan.com/api/sim/perception
- Or use the Python starter kit — typed client, 3 reference bots, MIT-licensed, runs in a Docker container: github.com/children-of-titan/sim-bot-starter
pip install git+https://github.com/children-of-titan/sim-bot-starter.git export SIM_API_KEY="sim_..." python -c " from simbot import Client state = Client().perception() print(f'Cash: ${state.player.cash:.2f} Rank: {state.player.rank}') " - Poll state — call
GET /api/sim/perceptionto get everything you need in one call. - Take actions — purchase investments, stake cash, fork, use powers. See the Recipes section below.
Authentication
All sim endpoints accept two authentication methods. Bots use API key auth; the browser UI uses session auth. Both have access to the same game actions.
API Key (Bots)
Authorization: Bearer sim_a1b2c3...
Requires active $19.99/mo subscription. Rate limited.
Session (Browser)
Cookie-based auth via NextAuth. Used by the game UI automatically. No API key needed for browser play.
One account, two identities (HUMAN + BOT)
A single account can compete on both surfaces at the same time. The identity is resolved per-request from the auth channel — your browser session always counts as HUMAN, your Bearer API key always counts as BOT. This applies to tournament registration, tournament creation, and all per-tournament actions.
- Register for a
HUMAN_ONLYtournament via session (you're playing yourself) — the entry is tied to the human channel and must be driven via session. - Register for a
BOT_ONLYtournament via Bearer (the bot runs the show) — the entry is tied to the bot channel and must be driven via Bearer. MIXEDtournaments accept either channel, but the channel you registered through is the one that has to play the round.BOT_ONLYadditionally requires an active Sim API subscription (otherwise the Bearer key wouldn't authenticate). Cancelling your subscription silently locks you out of bot tournaments (Bearer auth fails) but never prevents you from playing HUMAN_ONLY rounds via session.
What happens when you subscribe
- Your account's
actorKindflips toBOT. You're now badged as a bot on the public leaderboard. - You land on
/sim/welcomeright after Stripe checkout. This is the only page that shows your API key, and it shows it once. Set yourbotNameandbotAuthorUrlthere for leaderboard attribution. - Tournament-aware routing: if you're registered for an active tournament round, every
perception()and action call automatically targets that tournament. Otherwise you play the regular OPEN round. No code change needed when entering a tournament. - Public showcase URL:
/sim/bots/[botName]. Each bot has its own page with stats, tournament history, and a link to its author.
Rate Limits
All sim requests are rate limited per user. Subscribers (API key) get the higher tier; browser sessions get a lower tier sized for human play. Limits are enforced per 60-second rolling window.
API Key (Subscriber)
Session (Browser)
POST /api/sim/fork additionally caps at 30/min and POST /api/sim/power at 20/min — these stack on top of the tier limit above. All 429 responses include Retry-After, X-RateLimit-Remaining, and X-RateLimit-Reset headers.
Game Overview
The Sim is a block-based idle economy game. Each round lasts ~120,000 blocks (15 seconds per block, ~20 days). Players earn virtual currency by purchasing investments, staking cash, and leveraging card boosts. The goal: climb the leaderboard by maximizing net worth.
Investments
7 tiers of investments (Mining Rig → Centralized Exchange). Each earns cash per block. Higher tiers cost more but earn more. Costs scale exponentially: price * coefficient^owned.
Staking
Stake cash to earn the round's staking rate per block. Staking has a lockout period (~300 blocks) after each stake/unstake action. Auto-staking automatically stakes all earnings (requires card).
Fork (Prestige)
Reset all investments and staking for a permanent earnings multiplier. Bonus = netWorth / 5^forkCount. You choose a path on first fork (Production, Staking, or Dark Web) and draft bonuses each fork. Max 7 forks/round with a 1000-block cooldown between consecutive forks. Each path bonus has a per-pick stack cap and a path-wide ceiling — pure stacking of any single bonus caps below the ceiling.
Dark Web Powers
Spend encryption keys to use offensive/defensive powers: Blackout (blind a player), Immunity, Short Circuit (disable auto-stake), Cash Boost (+% net worth as cash), Unlimited Staking.
Cards & Boosts
Collectible cards provide permanent sim boosts: investment cost reductions, earnings multipliers, staking bonuses, starting cash, and more. Purchased with gems (real money via Stripe).
Events / News
Random events alter the economy: investment cost spikes, earnings boosts, staking rate changes, cash drops. Active events shift optimal strategy — monitor and adapt.
Net Worth Formula
netWorth = cash + stakedValue + investmentValueinvestmentValue = sum of (count * nextCost) for each owned investment type. Leaderboard ranks by net worth.
Tournament Bot API
Tournaments are a separate play surface from the OPEN sim. Calls to /api/sim/* always target the OPEN round; tournament rounds are reached through the trainer proxy under /api/trainer/tournament/{roundId}/*. The Bearer-API-key auth model is identical — your subscription token works on both surfaces.
Entry Modes
BOT_ONLY — requires actorKind=BOT (subscription). HUMAN_ONLY — browser session only. MIXED — both. The server enforces this on every register, purchase, stake, fork, contribute, and power action.
Visibility
PUBLIC — listed and indexable. UNLISTED — reachable only via direct URL or inviteCode. PRIVATE — invite-only whitelist; enumeration blocked.
Prize Pools
Two layers: Gems (always; seeded by creationFee + grown by per-entrant entryFeeGems) and Gold (optional; seeded once via creationFeeGold, no entry-fee growth). Both distribute by the same per-rank prizeDistributionJson. Tournament winners do not receive sim tickets.
Feature Flags
Each round can set disableCardBoosts, disableDarkWeb, or disableFork. Read them off the round object and gate your strategy accordingly — the server returns 400 on disabled action attempts.
Tournament loop (Python)
from simbot import Client
with Client() as client:
# 1. Find an eligible tournament
rounds = client.list_tournaments(entry_mode="BOT_ONLY")
rnd = next(r for r in rounds if r["status"] in {"PENDING", "ACTIVE"})
# 2. Register (idempotent — safe on every restart)
client.register_for_tournament(rnd["id"], bot_name="GreedyBot")
# 3. Drive the round with the same client methods as solo,
# passing tournament_id= on every call.
state = client.perception(tournament_id=rnd["id"])
client.purchase(investment_id=0, quantity=5, tournament_id=rnd["id"])
client.stake(amount=100.0, tournament_id=rnd["id"])
client.fork(path="PRODUCTION", bonus_id="PROD_EARNINGS_1", tournament_id=rnd["id"])
client.contribute(amount=50.0, tournament_id=rnd["id"])Create a tournament with a Gold prize layer
client.create_tournament(
title="Friday Bot Bash",
entry_mode="BOT_ONLY", # BOT_ONLY | HUMAN_ONLY | MIXED
creation_fee_gems=500, # >= 250; burned and seeds the gem pool
creation_fee_gold=10_000, # optional in-game Gold seed
total_blocks=60_000,
min_participants=15, # server clamps to >= 15
entry_fee_gems=0, # per-player; pools into prize
prize_distribution_json='{"1": 0.5, "2": 0.3, "3": 0.2}',
platform_fee_bps=0, # <= 5000 = 50% max
)Eligibility + abuse-prevention rules (creation)
- Creator must have finished ≥ 3 completed solo rounds (counted from
SimLeaderboard). - Max 1 active/pending tournament per creator.
- Max 3 tournament creations per rolling 168-hour window.
- Minimum 250 gems creation fee.
min_participantsfloor is 15. - Percentage
prize_distribution_jsonrequires every value strictly < 1.0 (so{"1": 1}reads as "1 absolute gem to rank 1"). Single-rank weight capped at 80%.
Response-shape note
The trainer's/api/trainer/tournament/{id}/perception returns the play-page full-state DTO (top-level state, forkConfig, events.past) — NOT the solo perception shape (player, fork, events.recent). The Python starter kit's PerceptionState parser normalizes both transparently. Hand-rolled clients should either consume the trainer shape directly or apply a similar shim.Bot Strategy Tips
Use /api/sim/perception — it returns everything in one call with pre-calculated affordability, cost multipliers, and fork analysis. One call every few blocks is enough.
Invest aggressively early — buy the cheapest investment with the highest earnings/cost ratio. Tier 1 investments (Mining Rig, Validator Node, Oracle) compound quickly.
Monitor events — cost reductions on specific investments are buying opportunities. Earnings boosts mean hold, not fork. Check events.active in the perception payload.
Fork timing matters — fork when your net worth is high but growth is slowing. The bonus formula (netWorth / 5^forkCount) means early forks give disproportionate boosts.
Path selection is permanent — Production path boosts investment earnings, Staking path improves staking returns, Dark Web path reduces power costs and gives extra keys. Choose based on your strategy.
Staking lockout — don't stake/unstake frequently. Plan your staking around the ~300 block lockout. Use Unlimited Staking power if you need rapid adjustments.
Endpoint Reference
Game State
/api/simBothreadFull game state — round info, player state, investments, fork config, leaderboard, path system, event effects.
/api/sim/perceptionSubscriptionreadBot-optimized single-call state. Returns everything needed to make decisions: round, player, investments with costs/earnings, events, fork analysis, dark web powers, staking info, leaderboard.
Subscription-only — session users receive 403. Recommended for bots; replaces multiple GET calls with one structured payload. ONLY returns the OPEN-game round — tournaments are a separate surface (see Tournament Bot API below) and require an explicit tournament_id / a different URL.
Investments
/api/sim/purchaseBothactionBuy an investment or unlock a new investment tier.
{ "investmentId": 0, "action": "buy", "quantity": 1 }
// action: "buy" | "unlock" • investmentId: 0–6 • quantity: ≥1 (buy only)Staking
/api/sim/stakeBothactionStake or unstake cash. Staked cash earns the round staking rate per block.
{ "action": "stake", "amount": 100.5 }
// action: "stake" | "unstake" • amount: positive numberStaking has a lockout period (~300 blocks). Use the Unlimited Staking dark web power or the Staking path to reduce/bypass it.
/api/sim/autostakeBothreadGet current auto-stake status.
/api/sim/autostakeBothactionToggle auto-staking on/off. When enabled, all earnings are automatically staked.
{ "enabled": true }Requires the Auto-Staker (Titanbot) card to unlock.
Fork (Prestige)
/api/sim/forkBothactionPerform a fork — reset investments for a permanent earnings bonus. First fork requires path selection; every fork requires bonus draft selection. Subject to a 1000-block cooldown between consecutive forks.
{ "path": "PRODUCTION", "bonusId": "PROD_EARNINGS_1" }
// path: "PRODUCTION" | "STAKING" | "DARK_WEB" (first fork only)
// bonusId: string (required — must be one of the draft options returned)Call without body first to get available paths/bonuses. The response is { requiresPathSelection, availablePaths } or { requiresBonusSelection, availableBonuses }. The bonus draft contains ALL eligible bonuses for your path/forkCount (3-5 typical). Reading perception.fork.cooldownBlocksRemaining tells you when the next fork is allowed (0 = ready). Bonus IDs follow the SCREAMING_SNAKE_CASE convention defined in PATH_BONUS_CONFIG.
Dark Web Powers
/api/sim/powerBothreadList available dark web powers, costs, your ticket balance, and targetable players.
/api/sim/powerBothactionUse a dark web power. Costs encryption keys (tickets).
{ "power": "blackout", "targetUserId": "user_abc123" }
// Wire values (camelCase): blackout, immunity, shortCircuit, cashBoost, unlimitedStaking
// targetUserId required for: blackout, shortCircuitEncryption Keys
/api/sim/buy-keysBothreadGet key purchase options and current key/gold balance.
/api/sim/buy-keysBothactionBuy encryption keys with Gems. Keys are used to activate dark web powers.
{ "quantity": 10 }
// Valid quantities: 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000
// Cost: quantity * 3 gemsReal-Time Stream
/api/sim/streamSubscriptionreadServer-Sent Events stream. Pushes real-time game events: block advances, event starts/ends, round end, powers received, and keepalive pings.
Subscription-only SSE — stays open. Events: block:advance, event:start, event:end, round:end, power:received, ping. Use EventSource API or equivalent.
Events & News
/api/sim/eventsBothreadGet active, upcoming, and past newsfeed events plus your current event effects (earnings/cost multipliers).
// Query params: ?limitActive=10&limitUpcoming=5&limitPast=10
Paths & Bonuses
/api/sim/pathsBothreadGet all path options, your selected path and bonuses, aggregated path effects, and the full bonus pool with stack caps and effect-type ceilings.
Each path has multiple repeatable bonuses with per-pick stackCaps. Effect totals are bounded by per-effectType ceilings (EFFECT_TYPE_CEILINGS): EARNINGS_MULT 0.35, STAKING_RATE 0.35, COST_REDUCTION 0.30, UNLOCK_REDUCTION 0.40, STAKING_LOCKOUT 400 blocks, POWER_COST -3, POWER_DURATION +0.75, KEYS_PER_FORK +4. To reach an earnings/staking ceiling of +35% you must combine PROD_EARNINGS_1/STAKE_RATE_1 (capped at +25%) with the one-shot PROD_EARNINGS_2/STAKE_RATE_2 (+10%). Dark Web's DARK_EARNINGS_1 caps at +15% earnings — a deliberate ceiling below the dedicated economy paths.
Leaderboard
/api/sim/leaderboardBothreadGet the round leaderboard with ranks, usernames, and net worth.
Contribution & Misc
/api/sim/contributeBothactionContribute cash to the round contribution goal. When the goal is met, the round ends early.
{ "amount": 50.0 }/api/sim/dismissBothactionDismiss a short circuit notification (marks it as seen).
/api/sim/cardsBothreadGet your cards with sim boost effects — earnings boosts, cost reductions, staking bonuses, etc.
/api/sim/profileBothreadGet your sim profile — lifetime stats, top finishes, tickets, etc.
/api/sim/historyBothreadGet your transaction history for the current round.
API Subscription
/api/sim/api-subscription/checkoutBrowser onlyactionCreate a Stripe checkout session for the $19.99/mo API subscription. Returns a URL to redirect to.
Session auth only — must be logged in via browser to subscribe. After checkout you land on /sim/welcome to reveal your key once.
/api/sim/api-subscription/statusBrowser onlyreadGet your current API subscription status, key prefix, creation date, and bot attribution fields (actorKind, botName, botAuthorUrl).
/api/sim/api-subscription/onboarding-keyBrowser onlyactionFirst-reveal endpoint used by /sim/welcome immediately after Stripe checkout. Returns the API key once. Refuses replay (apiKeyOnboardedAt is set after the first call).
Session auth only. Calling this a second time returns 400 with code RULE_VIOLATION. Use the regenerate endpoint if you need a new key (it invalidates running bots).
/api/sim/api-subscription/keyBrowser onlyactionRegenerate your API key. Invalidates the previous key immediately.
Session auth only. The new key is returned once — store it securely. Will break any bot still using the old key.
/api/sim/api-subscription/profileBrowser onlyactionUpdate your bot's display name and author URL. These show on the public leaderboard and on /sim/bots/[botName].
{ "botName": "greedy_v2", "botAuthorUrl": "@yourhandle" }
// botName: 1-32 chars, [A-Za-z0-9 _-.]
// botAuthorUrl: URL or @handle, max 128 chars
// pass null to either to clear it/api/sim/api-subscription/portalBrowser onlyactionCreate a Stripe Customer Portal session to manage billing, cancel subscription, etc.
Tournaments
/api/sim/tournamentsPublicreadList upcoming, active, and recent tournaments. Each entry includes the prize pool, entrant count, and (if authed) your registration status.
No auth required, but pass session/API auth to enrich each entry with your `myRegistration` status.
/api/sim/tournaments/[roundId]PublicreadTournament detail — round info, prize pool, full roster, live or final leaderboard. The leaderboard is filtered to entrants only.
/api/sim/tournaments/[roundId]/registerBothactionRegister for a tournament. Identity is sourced from the auth channel: a browser session registers you as HUMAN, a Bearer API key registers you as BOT. The same User can enter HUMAN_ONLY tournaments via session AND BOT_ONLY tournaments via Bearer — they are separate per-tournament identities tied to the same account.
Eligibility: BOT_ONLY rounds require Bearer auth (= BOT actorKind) AND an active Sim API subscription. HUMAN_ONLY rounds require session auth (= HUMAN actorKind). MIXED accepts either. Refuses if the registration window is closed or you're already registered. Once registered, you must drive the round through the matching channel — a HUMAN_ONLY entry can only be played via session, a BOT_ONLY entry via Bearer.
/api/sim/tournaments/[roundId]/registerBothactionWithdraw your registration. Allowed only while the round is still PENDING — once it goes ACTIVE you're locked in. Refunds entryFeePaidGems atomically.
Use fetch with method: 'DELETE'. Browser <form> elements don't natively support DELETE, so this can't be hit from a plain HTML form. Either channel can withdraw — the entry isn't bound to the channel that created it.
/api/sim/tournaments/createBothactionCreate a new tournament round programmatically. Atomically deducts creationFeeGems (and optionally creationFeeGold) and seeds them into the prize pool. Subject to creator cooldowns: max 1 active/pending per user, max 3 creations per 168h, requires 3+ completed solo rounds. Creator identity (for auto-register) is sourced from the auth channel — session = HUMAN, Bearer = BOT — so a subscribed user creating via browser is auto-registered as HUMAN if the round permits.
{
"title": "Friday Bot Bash",
"entryMode": "BOT_ONLY", // BOT_ONLY | HUMAN_ONLY | MIXED
"visibility": "PUBLIC", // PUBLIC | UNLISTED | PRIVATE
"creationFeeGems": 500, // ≥ 250 — seeds the gem pool
"creationFeeGold": 0, // optional in-game Gold seed
"totalBlocks": 60000, // 1000–1,000,000
"blockLength": 15, // 5–300 seconds
"minParticipants": 15, // server clamps to ≥15
"entryFeeGems": 0, // per-player; pools into prize
"prizeDistributionJson": "{\"1\": 0.5, \"2\": 0.3, \"3\": 0.2}",
"platformFeeBps": 0, // ≤ 5000 = 50% max
"disableCardBoosts": false,
"disableDarkWeb": false,
"disableFork": false,
"registrationOpensAt": "2026-05-20T00:00:00Z", // ISO 8601, optional
"registrationEndsAt": "2026-05-21T00:00:00Z"
}Percentage mode requires every distribution value < 1.0 strictly; '{"1": 1}' is read as '1 absolute unit' not '100%'. Single-rank weight capped at 80% in percentage mode. Bots can call this via the trainer proxy at POST /api/trainer/tournament with the same body.
Tournament Bot API
/api/trainer/tournamentBothreadList active and pending tournament rounds. Filterable by entryMode + visibility. Authed visibility gate hides PRIVATE rounds unless you're the creator or a non-revoked invitee.
// Query params: // ?entryMode=BOT_ONLY | HUMAN_ONLY | MIXED // ?visibility=PUBLIC | PRIVATE (UNLISTED filter is rejected — falls back to default)
The trainer proxy at /api/trainer/* forwards Bearer-API-key calls to the moon-trainer service with X-Actor-Kind=BOT|HUMAN derived from your subscription. Bots see the same list a human sees, scoped by visibility.
/api/trainer/tournamentBothactionCreate a new tournament round via the trainer (bot-friendly alias for POST /api/sim/tournaments/create). Same body, same validation, same gem + gold deduction.
// See POST /api/sim/tournaments/create body schema — identical.
/api/trainer/tournament/mineBothreadList tournaments you created (creator dashboard). Capped at 100; inviteCode and prizeDistributionJson are stripped from the list view (visible on detail).
/api/trainer/tournament/{roundId}BothreadFull state for a tournament round — round metadata, your player state, investments with multipliers, fork config, leaderboard, path system, gem + gold prize breakdown. Mirrors GET /api/sim shape for the round-state surface.
Response includes round.prizePoolGems, round.prizePoolGold, round.prizeDistribution (per-rank gem ladder), and round.goldDistribution (per-rank gold ladder, null when no gold layer). Tournament winners do NOT receive sim tickets — tickets are an OPEN-sim mechanic.
/api/trainer/tournament/{roundId}/perceptionBothreadTournament perception — alias for the full-state endpoint above with the bot-optimized name. Same payload shape.
The trainer's tournament `perception` returns the play-page full-state DTO (top-level `state`, `forkConfig`, `events.past`), NOT the solo-shape (`player`, `fork`, `events.recent`). The Python starter kit's PerceptionState parser normalizes both shapes transparently.
/api/trainer/tournament/{roundId}/registerBothactionRegister for a tournament. Atomically deducts entryFeeGems and pools the fee into prizePoolGems. EntryMode gate: BOT_ONLY rejects HUMAN actorKind and vice versa; MIXED accepts both. PRIVATE rounds require an invite; UNLISTED rounds require inviteCode in the body.
{
"botName": "greedy_v2", // optional, 1–64 chars
"botAuthorUrl": "@yourhandle", // optional, ≤256 chars
"inviteCode": "abc123" // required only for UNLISTED rounds with a code
}Idempotent — calling on an already-registered entry returns the existing row without re-deducting the fee.
/api/trainer/tournament/{roundId}/registerBothactionWithdraw your registration and refund entryFeePaidGems atomically. Allowed only while the round is PENDING. You can re-register before startTime.
/api/trainer/tournament/{roundId}/purchaseBothactionBuy units of an investment in a tournament round.
{ "investmentId": 0, "quantity": 5 }/api/trainer/tournament/{roundId}/unlockBothactionUnlock an investment tier in a tournament round.
{ "investmentId": 3 }/api/trainer/tournament/{roundId}/stakeBothactionStake cash in a tournament round.
{ "amount": 100.0 }/api/trainer/tournament/{roundId}/unstakeBothactionUnstake cash in a tournament round.
{ "amount": 50.0 }/api/trainer/tournament/{roundId}/forkBothactionPerform a fork in a tournament round. Same path/bonus-draft flow as solo. Rejected with 400 when round.disableFork is set.
{ "path": "PRODUCTION", "bonusId": "PROD_EARNINGS_1" }/api/trainer/tournament/{roundId}/powerBothactionUse a dark-web power in a tournament round. Rejected with 400 when round.disableDarkWeb is set. Power wire values are camelCase (blackout, immunity, shortCircuit, cashBoost, unlimitedStaking).
{ "power": "immunity" }
// or { "power": "blackout", "targetUserId": "user_abc" }/api/trainer/tournament/{roundId}/contributeBothactionContribute cash to the tournament round's contribution goal. Triggers prize distribution when the goal is met.
{ "amount": 250.0 }/api/trainer/tournament/{roundId}/autostakeBothactionToggle auto-staking in a tournament round. Requires Titanbot card (same as solo).
{ "enabled": true }/api/trainer/tournament/{roundId}/dismissBothactionDismiss the short-circuit notification for a tournament round.
/api/trainer/tournament/{roundId}/buy-keysBothreadTournament-scoped key-purchase options. currentKeys reads from TournamentProfile.tickets (round-isolated).
/api/trainer/tournament/{roundId}/buy-keysBothactionBuy encryption keys with Gems for an active tournament round. Keys land on TournamentProfile.tickets and transfer to SimProfile.tickets at round finalization. Open to both humans and bots — dark-web powers are core gameplay.
{ "quantity": 10 }
// Valid quantities: 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000
// Cost: quantity * 3 gems/api/trainer/tournament/{roundId}/eventsBothreadActive + upcoming + past newsfeed events for a tournament round.
/api/trainer/tournament/{roundId}/leaderboardBothreadTournament leaderboard. Filtered to entrants only.
// Query params: ?limit=100 (default 100, max 100, NaN safely defaults to 100)
/api/trainer/tournament/{roundId}/pathsBothreadPath system status for a tournament round — selected path, drafted bonuses, aggregated effects.
/api/trainer/tournament/{roundId}/cardsBothreadYour card collection + active boost summary scoped to this tournament. boostSummary is zeroed when round.disableCardBoosts=true.
/api/trainer/tournament/{roundId}/powerBothreadAvailable dark-web powers + tournament-scoped key balance + targetable rivals. Powers respond per-round (tickets from TournamentProfile, usage counters from TournamentPlayerState, leaderboard scoped to this round).
/api/trainer/tournament/{roundId}/invitesBothreadList invitees for a PRIVATE tournament. Creator only.
/api/trainer/tournament/{roundId}/invitesBothactionInvite a user to a PRIVATE tournament. Creator only.
{ "invitedUserId": "usr_alice" }/api/trainer/tournament/{roundId}/invites/{invitedUserId}BothactionRevoke a PRIVATE-tournament invite. Creator only.
Public Explorer & Bot Showcase
/api/explorer/leaderboardBothreadLeaderboard with per-player state. Powers the /sim/explorer page (humans / bots / mixed filter, round history). Mintopoly-explorer compatible body, but now auth-gated — per-player cash, investments, and active cards are competitively sensitive and should not be anonymously scrapable.
// Query params: // ?round=N required — mintopoly round number // ?actor=all|human|bot default 'all' — bot/human/mixed filter // ?sliceStart=0 pagination offset // ?sliceEnd=100 pagination end (max 500 in practice) // ?live=1 live tally (skip the 10-min cron snapshot)
Returns rank, player {id, username, actorKind, botName, botAuthorUrl}, netWorth, cashOnHand, investmentsOwned, activeCards, forks {number, bonus, lastForkBlock}, contributions, lastTally {earningsPerBlock, stakedValue}. NOTE: forks now exposes `lastForkBlock` (most recent fork block) instead of the full `onBlocks` array — the full history leaked competitive timing. When actor=human or actor=bot, `rank` is the position within the filtered subset (rank 1 = top bot), NOT the global rank. Re-query with actor=all to get global ranks.
/api/explorer/round/[round]BothreadRound metadata + aggregate totals (allCash, allStaking summed across all players, totalPlayers, contribution goal/progress, status, rewards). Auth-gated — aggregates leak the round's total-economy size.
/api/explorer/roundsBothreadList all rounds with player counts (used by the explorer's round selector). Auth-gated.
/api/explorer/round/activeBothreadReturns the currently-active round number, or the latest ENDED round if none is live. Auth-gated.
/api/explorer/player/[userId]BothreadPer-player profile + round-by-round state. Auth-gated (session or Bearer key) since per-round cash, investments, active cards, and fork timing are competitively sensitive. Looks up by user id (wallet-address lookup retained server-side for backwards-compat with old shared links).
forks exposes `lastForkBlock` only; the full forkOnBlocks history is no longer returned.
/api/explorer/player/[userId]/resultsBothreadPer-player historical results for ENDED rounds — rank, netWorth, final investment composition, fork bonuses earned. Auth-gated.
/api/explorer/player/rankBothreadPlayer rank lookup for a specific round. Auth-gated.
// Query params: // ?address=<userId or walletAddress> required // ?round=N required — mintopoly round number
/api/explorer/tournamentsBothreadAuth-gated list of tournament rounds for the explorer surface. Lists PUBLIC + UNLISTED tournaments grouped by Active / Upcoming / Past; PRIVATE rounds are hidden. Mirrors the solo /api/explorer/leaderboard auth model — competitive game state behind a sign-in or Bearer key.
// Query params: // ?entryMode=BOT_ONLY|HUMAN_ONLY|MIXED filter by entry mode // ?sponsored=1 only return tournaments with a sponsor
/api/explorer/tournaments/[id]BothreadAuth-gated detail view for a single tournament — round metadata, entry roster, and per-player leaderboard with the same shape solo /api/explorer/leaderboard returns (cashOnHand, investmentsOwned, activeCards, contributions, lastTally.{earningsPerBlock, stakedValue}, forks.{number, bonus, lastForkBlock}). PRIVATE tournaments return 404.
forks exposes `lastForkBlock` only; the full forkOnBlocks history is never emitted on either explorer surface.
/api/sim/bots/[botName]PublicreadPublic bot showcase profile — stats, tournament history, recent rounds. Case-insensitive lookup on botName.
Only matches users with actorKind=BOT. botName is unique at the schema level so the URL always resolves to one bot.
Gems & Shop
/api/gems/balanceBothreadGet your current gem balance and last 20 gem transactions.
/api/gems/checkoutBothactionCreate a Stripe Checkout session for gem purchases. Returns a URL — open it in a browser to complete payment.
{ "packageId": "explorer" }
// Package IDs: starter ($4.99, 500 gems), explorer ($9.99, 1100), commander ($24.99, 3000), admiral ($49.99, 6500), titan ($99.99, 15000)Returns { url } — bots should open this URL externally or provide it to the operator. Gems are credited automatically via Stripe webhook.
/api/shopBothreadList all cards available for purchase with gem prices, rarity, boost effects, and how many you own.
/api/shopBothactionPurchase a card with gems. Cards provide sim boosts: earnings multipliers, cost reductions, staking bonuses, auto-staking, and more.
{ "cardId": "card_id_here", "quantity": 1 }
// quantity: 1–10Cards boost sim performance. Use GET /api/sim/cards to see active boosts. Card effects are snapshot at round join.
Error Handling
All endpoints return JSON. Errors include both a human-readable error string and a stable machine-readable code. Bots should branch on code; error wording can change.
Bad request — invalid parameters, insufficient funds, game rule violation.
Unauthorized — missing or invalid API key / session.
Subscription required — endpoint is bot-only.
Rate limited — too many requests. Check Retry-After header.
Server error — unexpected failure. Retry after a short delay.
// Example error response
{
"error": "Not enough cash to purchase this investment",
"code": "INSUFFICIENT_CASH"
}Stable error codes
Branching on these is safe. New codes may be added; existing codes never rename or disappear.
# Python — branch on stable codes, not strings
import httpx
resp = httpx.post(
"https://www.childrenoftitan.com/api/sim/purchase",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"investmentId": 0, "action": "buy", "quantity": 1},
)
if resp.status_code != 200:
body = resp.json()
code = body.get("code")
if code == "INSUFFICIENT_CASH":
# Wait, stake unstake, or buy a cheaper tier
...
elif code == "RATE_LIMIT_EXCEEDED":
# Honor Retry-After header
retry_after = float(resp.headers.get("Retry-After", "5"))
...
else:
# Log unknown — your code path doesn't handle it
print(f"Unhandled: {code} — {body.get('error')}")Recipes
Common bot patterns, copy-pasteable. All examples assume you've installed the starter kit (simbot) and set SIM_API_KEY.
Greedy buy — best earnings/cost ratio
from simbot import Client
client = Client()
state = client.perception()
candidates = [i for i in state.investments if i.unlocked and i.can_afford]
if candidates:
target = max(candidates, key=lambda i: i.earnings_per_unit / i.next_cost)
client.purchase(investment_id=target.id, quantity=1)Stake all spare cash above a buffer (when lockout is clear)
if state.staking and state.staking.can_stake:
BUFFER = 1_000.0
if state.player.cash > BUFFER:
client.stake(amount=state.player.cash - BUFFER)Fork when projected bonus exceeds 5%
if state.fork and state.fork.can_fork and state.fork.projected_bonus_percent >= 5.0:
result = client.fork()
if result.get("requiresPathSelection"):
# First fork: pick a path. PRODUCTION is a safe default.
result = client.fork(path="PRODUCTION")
if result.get("requiresBonusSelection"):
# Draft a bonus from the offered options
bonus_id = result["availableBonuses"][0]["id"]
client.fork(path="PRODUCTION", bonus_id=bonus_id)React to events live with the SSE stream
with client.stream() as events:
for event in events:
if event.type == "block:advance":
handle_block(event.data["block"])
elif event.type == "event:start":
print(f"New event: {event.data['event']['title']}")
elif event.type == "power:received":
print(f"You got hit by {event.data['power']}")Defensive: buy IMMUNITY when blacked out
from simbot import DarkWebPower
if state.player.is_blacked_out and not state.player.has_immunity:
immunity = next(
(p for p in state.powers if p.id == DarkWebPower.IMMUNITY),
None,
)
if immunity and immunity.can_afford:
client.use_power(DarkWebPower.IMMUNITY)Robust loop — handle round transitions and rate limits
import time
from simbot import Client, NoActiveRoundError, RateLimitError, SimBotError
with Client() as client:
while True:
try:
state = client.perception()
if not state.active:
time.sleep(300)
continue
run_strategy(client, state) # your code
except NoActiveRoundError:
time.sleep(300)
except RateLimitError as exc:
time.sleep(exc.retry_after_seconds or 30)
except SimBotError as exc:
print("API error:", exc)
time.sleep(15)Perception Payload Example
Response from GET /api/sim/perception — the recommended polling endpoint for bots.
{
"active": true,
"round": {
"id": "clxyz...",
"number": 5,
"currentBlock": 4200,
"totalBlocks": 120000,
"blocksRemaining": 115800,
"blockLengthSeconds": 15,
"stakingRate": 0.001,
"contributionGoal": 50000,
"currentContributions": 6000,
"contributionProgress": 0.12,
"startTime": "2026-03-10T00:00:00.000Z"
},
"player": {
"cash": 1523.45,
"stakedValue": 500.0,
"investmentValue": 3200.80,
"netWorth": 5224.25,
"earningsPerBlock": 12.34,
"rank": 7,
"totalPlayers": 142,
"forkNumber": 2,
"forkBonus": 0.045,
"forkBonusPercent": 4.50,
"path": "PRODUCTION",
"pathBonuses": ["PROD_EARNINGS_1", "PROD_COST_1"],
"tickets": 15,
"isBlackedOut": false,
"hasImmunity": false,
"autoStakeEnabled": true,
"shortCircuit": null
},
"investments": [
{
"id": 0,
"name": "Mining Rig",
"tier": 1,
"owned": 25,
"unlocked": true,
"nextCost": 342.50,
"canAfford": true,
"earningsPerUnit": 0.85,
"totalEarnings": 21.25,
"costMultiplier": 1,
"earningsMultiplier": 1.2,
"coefficient": 1.15
}
],
"events": {
"active": [
{
"id": "evt_123",
"title": "Mining Boom",
"type": "EARNINGS_BOOST",
"investmentId": 0,
"value": 1.5,
"blocksRemaining": 200
}
],
"upcoming": [
{ "title": "Market Crash", "blocksUntil": 50 }
],
"recent": [
{ "id": "evt_old", "title": "Mining Slump", "type": "EARNINGS_REDUCTION", "investmentId": 0, "value": 0.7, "endBlock": 4100 }
]
},
"fork": {
"canFork": true,
"projectedBonus": 0.021,
"projectedBonusPercent": 2.09,
"formula": "netWorth / 5^2",
"needsPathSelection": false,
"bonusDraftPreview": [
{ "id": "PROD_EARNINGS_2", "name": "Production Mastery", "description": "..." }
]
},
"darkWeb": {
"powers": [
{ "id": "blackout", "name": "Blackout", "cost": 3, "canAfford": true, "requiresTarget": true }
],
"targets": [
{ "userId": "user_abc", "username": "rival_bot", "netWorth": 8000, "rank": 3 }
]
},
"staking": {
"baseRate": 0.001,
"effectiveRate": 0.0012,
"lockoutBlocksRemaining": 0,
"canStake": true,
"canUnstake": true,
"hasUnlimitedStaking": false,
"unlimitedStakingEndsIn": 0
},
"leaderboard": [
{ "rank": 1, "username": "whale_01", "netWorth": 50000, "isYou": false }
],
"meta": {
"tick": 4200,
"timestamp": "2026-03-12T14:30:00.000Z",
"nextBlockIn": 8.5
}
}