API for Tools

The game has an official HTTP API, so tools never need to scrape pages. It sends and receives JSON. Anyone may build their own front end or tool on it, on any address: see Connecting from another site.

RequestWhat it does
GET /worldAge, current time, tick count, number of houses
GET /rulesThe age's slots, definitions, fingerprints and numbers; academic attributes and general traits with their effects as [stat, bp]; races' flags by name; refining recipes
GET /rankingsThe 100 largest houses by land, largest first (ties by house id). Not the age result: a state wins the age on score; see The End of an Age and /ages/{age}
GET /houses/{id}Your own house in full; any other house's public facts (name, address, race, land, renown, might, protected, and robot: played by a robot, not a person)
GET /states/{realm}/{state}A state's houses (public facts)
GET /states/{realm}/{state}/seriesA state's totals over time, recorded after every tick: ?from={tick}&to={tick} (default: the whole age so far) → {"realm", "state", "ticks": [...], "houses": [...], "land": [...], "might": [...], "renown": [...]}. At most 500 points: a longer span is thinned evenly, keeping the last. A tick before the state had a house counts zeros.
GET /marketEvery material: producing realms, output this tick, last trade (price, and units and gold traded in its tick), reference price (see Trade), stewards (their offer of unclaimed output, {"price", "quantity"}, or null; see Trade), open orders by price
GET /realms/{realm}A realm's material, and each state's share, house count, leader and ranks (as GET /state gives them; null for a state with no house)
GET /stateYour state: members, leader, vote counts, treasury, war recovery ({"share_bp", "until_tick"} or null) and peace_dividend ({"from_tick", "active"}, or null while at war), and ranks: where the state stands among every state with a house, by land, might and renown, each {"total", "rank", "of"} (the total over its houses; 1 is first; ties share a rank) (signed in)
GET /colloquiumYour state's Colloquium: projects, tiers, knowledge, decay, contributors (signed in)
GET /hallThe Hall of Deeds: every listing, with the character's profile and record, and auction state
GET /offersOffers you've made and offers on your characters (signed in). A character you made an offer on shows its full profile only while it's listed; otherwise its name and owner
GET /ageThe age: its name, when it starts (started_at, ms) and whether it has (started), the next age once planned (next: {"age", "name", "ruleset", "start_at"} or null), tick, when the next tick is due (next_tick_at, ms) and the tick length (tick_ms), ticks left, scoring weights, live state standings, and the result once it has ended. Each row of standings and of the result's states has the state's name
GET /agesEvery recorded age, oldest first: {"age", "name", "started_at", "ended_at", "winner", "winner_name"}
GET /ages/{age}An ended age's result: winner, state scores, house rankings, heirs kept
GET /age/recapThe age so far, for a replay: frames, snapshots taken evenly over the age, each {"tick", "at", "states": [[realm, state, land, might, war_points]], "houses": [[house, land]]} (the ten largest houses); wars, ended ones first, then any still under way (their end, ended_tick and winner null); and names (realms, states by "realm:state", houses by id)
GET /ages/{age}/recapThe same for an ended age, with one last frame at its end
GET /heirsYour heirs waiting in your account, across ages (signed in)
GET /me/agesYour own record in every ended age you played, latest first: your house, its state (with its name), whether the state won, your ranks by land, might and renown (land_rank, might_rank, renown_rank, out of houses), your state's state_rank (out of states) and state_score, and the heirs your house kept. Only you see it (signed in)
POST /heirs/recallBring one into your house this age: {"uid": "1-G4"} (signed in)
GET /chat/{channel}Chat on world, realm (yours) or state (yours): the last 200 messages, or those after ?after={id}. Each channel keeps its newest 1,000 for the current age. Each has channel and the sender's address in from (signed in)
POST /chat/{channel}Post to a channel: {"text": "..."}, 1-1,000 characters (signed in). /state/messages is the old name for the state channel.
GET /ticksWhat each of your house's last 48 ticks changed, newest first: the change in gold, food, peasants, soldiers, troops (soldiers included), land, incoming land, books, renown, horses, chariots, aether and adepts, each material's under materials, and every level after the tick under levels (signed in)
GET /liveA WebSocket of live updates: your house's news, chat on your three channels, and every tick with what it changed for your house as report (signed in, with a house; a connected site adds ?ticket= from POST /live/ticket). Each message is JSON with a type: news, chat, tick, or resync (reload what you show)
POST /live/ticket{"ticket"}: opens /live?ticket= once, within 30 seconds (signed in)
GET /warsWars under way (each with land_at_start: [a, b], the land each side held when it began), ceasefires, tribute, war points and past wars
GET /wars/{id}/seriesOne war's two sides over time, for a war under way or past: {"a", "b", "from_tick", "to_tick", "ticks": [...], "a_series": {"houses", "land", "might", "renown"}, "b_series": {...}}, from 12 ticks before it began to its end (or now). Thinned to at most 500 points like a state's series.
GET /relationsYour state's hostility meters both ways, ceasefire proposals and land ratios; your state's war (signed in)
GET /ordersYour open orders and your state's treasury orders, each with what it held until it fills: gold for a buy (with room for any cross-realm fee), goods for a sale (signed in)
GET /aid/quoteWhat aid would arrive as, for a house in your own state: ?to={house}&what={good}&amount={n} → {"arriving", "tax_bp", "loss_bp", "ticks"} (signed in). Goods: gold, food, soldiers, horses, aether, explorable, material:{id}
GET /namesName suggestions in a race's own style: ?kind=house&race={race} for founding a house (no sign-in), or ?kind=general / ?kind=academic for your house's race (signed in). count 1-10, default 5. {"names": [...]}, avoiding names already taken
GET /meYour account and your house this age (signed in; email is null for a connected site). ranks places the house among every house this age: land_rank, might_rank and renown_rank (1 is first; ties share a rank), out of houses. The house includes population and max_population (everyone it holds, and the most it can hold), pending, work under way soonest first ({"kind", "what", "count", "started_at", "done_at"}; kind is explore, build, train, medics or army), science.ranks per category (economy, military, arcane: rank into the age's science_ranks, books_each a scientist writes, experience_each books written per scientist, next_rank_at, and books_next_tick), jobs (jobs in your finished buildings) and employment_bp (your peasants against the workers those jobs need, at most 10000; building efficiency rises with it), adept_cap_tenths (adepts an acre your shrines gather up to, in tenths: 31 is 3.1; null for no limit), buildings_do, what each building does now and with 1% more of your land in it (stats [stat, bp now, bp then], produces and holds [product, now, then] a tick, living), heirs (your marked heirs, in order), general_trait_slots, resilience_bp (your Spell Resilience), vengeance (your Countercast Vengeance: [[{"realm", "state"}, bp], ...], one entry for each state that has tried hexes on you lately), rite_costs ([rite, aether, ticks, incense] for every rite: its aether cost for you now, how long a lasting one runs for you (0 ticks: it acts at once), and the incense it burns; a strong hex can cost more aether on a house your state is only unfriendly toward: rite_costs_unfriendly, [rite, aether], says how much), shrine_share_bp (your shrines as a share of your land), aether_capacity (the most aether your buildings hold), quest (your quest, the Truth unnamed: {"seeker", "seeker_kind" (academic, general or scientists), "episode", "of", "ask", "answer_by_tick", "next_ask_tick", "deadline_tick"}, or null), truths (the Truths you may use: {"id", "name", "kind", "ours"}), subscriber, wonders (World Wonders finished: {"id", "name", "house", "house_name", "state", "tick", "revived"}), wonder_works (your state's builds: {"id", "name", "builder", "gold", "gold_needed", "materials", "done_tick"}), realm_work (your realm's work: {"id", "name", "material", "output_bp", "proposed", "votes_needed", "votes_yes", "yes", "passed", "gold", "gold_needed", "material_given", "material_needed", "done"}, or null), market_fee_bp (the market fee after your trading houses' cut; only an order that fills against a waiting one pays it, and waiting orders pay none: a buy of N gold costs N + ceil(N × it / 10000), a sale of N gold pays you N − ceil(N × it / 10000), on the order's total, not each fill), and prices, your exact costs after modifiers (including upgrade, general, mercenary and refine; and offense, an army's bonuses: bp maps "none" or a general's id to [offense_bp, elite_offense_bp] with that general leading, and conds lists [stat, race, attack, bp] bonuses that hold now only against a race slot or for an attack kind (an index into params.attacks; null: any). An army's offense is apply(plain + mounts + apply(elite, elite_bp), offense_bp), each bp the general's plus the matching conds) with the most one order may ask for (exploring: gold_per_acre at your size, and soldiers_milli_per_acre: soldiers for N acres = ceil(N × it / 1000)). A cost is ceil(base × (10000 + bp) / 10000), never below 0: it rounds up, so splitting an order into smaller ones never makes it cheaper.
GET /newsYour house's news, newest first (signed in)
GET /reportsIntel reports from your state, newest first; each has a kind (survey, muster, ledgers, archives, couriers, dossier, and from divinations scrying, omens, roads). Add ?target={id} for one house (signed in)
POST /commandsGive an order (signed in; below)
GET /auth/connectionsYour connected sites: id, site, created_at, last_used_at, expires_at (ms) (signed in)
POST /auth/connections/{id}/endEnd one of them (signed in)
POST /auth/disconnectA connected site signs itself out: its token stops working

Connecting from another site

A front end or tool on its own address acts for a player once they allow it. It never handles their sign-in.

  1. Send the player to /auth/connect?site={origin}&return_to={url}&state={random} on the game's site. site is your origin (https://…, or http://localhost while developing); return_to is a page on it; state (up to 256 characters) comes back unchanged so you can match the answer to your request.
  2. The player signs in there if needed, sees your site's address and their house, and chooses Allow or Deny.
  3. They come back to return_to with the answer after the #: #token={token}&state={state}, or #error=denied&state={state}. Check state, then clear the address bar.
  4. Send the token with every request: Authorization: Bearer {token}. Any site may call the API this way (CORS is open; cookies are never used). For live updates, get a ticket from POST /live/ticket and open /live?ticket={ticket}.

A token lasts 30 days, until the player ends it, or until you call /auth/disconnect; then requests answer 401 and you connect again. It can do anything the player can do in the game, but it can't approve other sites, add passkeys or see the player's email.

Commands

Unit slots, wherever a command or reply lists units: 0 soldiers, 1 offense, 2 defense, 3 elite, 4 thieves, 5 offense+, 6 defense+, 7 elite+, 8 mercenaries, 9 elite++. A shorter list means 0 for the rest. In an attack, units[8] hires that many mercenaries for the attack (see Military). A train uses soldiers unless direct recruits straight from peasants. Spy reports show troops by kind instead: {"soldiers", "offense", "defense", "elites"}, base and upgraded together, thieves not shown.

Names are 1 to 40 characters for houses and states, and 1 to 30 for generals and academics. None may contain < or >, control characters or text-direction marks. Names come from players, so treat every one as plain text: escape it before you put it into HTML.

{"type": "create_house", "name": "House Vale", "race": "dwarf", "personality": "sage"}
{"type": "create_house", "name": "House Vale", "race": "dwarf", "personality": "sage", "realm": 4, "state": 2}
{"type": "explore", "house": 0, "acres": 20}
{"type": "set_draft", "house": 0, "rate_bp": 2500}
{"type": "release", "house": 0, "unit": 0, "count": 500}
{"type": "set_adept_cap", "house": 0, "per_acre_tenths": 31}
{"type": "set_adept_cap", "house": 0, "per_acre_tenths": null}
{"type": "release_adepts", "house": 0, "count": 50}
{"type": "train", "house": 0, "unit": 3, "count": 100}
{"type": "train", "house": 0, "unit": 1, "count": 100, "direct": true}
{"type": "attack", "attacker": 0, "target": 5, "units": [0, 100, 0, 50, 0, 0, 0, 20], "medics": 10, "general": 1}
{"type": "attack", "attacker": 0, "target": 5, "units": [0, 0, 0, 80, 0, 0, 0, 0], "kind": "sack"}
{"type": "attack", "attacker": 0, "target": 5, "units": [0, 120, 0, 0, 0, 0, 0, 0], "kind": "flanked_ambush", "building": "farms"}
{"type": "transmute", "house": 0, "from": "amber", "to": "sulfur", "amount": 50}
{"type": "start_wonder", "house": 0, "wonder": "paradise_of_trees"}
{"type": "fund_wonder", "house": 1, "builder": 0, "gold": 250000, "material": "pine", "amount": 500}
{"type": "revive", "house": 0}
{"type": "upgrade", "house": 0, "unit": 3, "count": 20}
{"type": "train_medics", "house": 0, "count": 30}
{"type": "refine", "house": 0, "recipe": "timber", "times": 4}
{"type": "recruit_general", "house": 0, "name": "Vess"}
{"type": "recruit_general", "house": 0, "name": "Vess", "traits": ["bellator", "virtuic"]}
{"type": "set_defender", "house": 0, "general": 1}
{"type": "build", "house": 0, "building": "farms", "count": 10}
{"type": "raze", "house": 0, "building": "homes", "count": 2}
{"type": "build_chariots", "house": 0, "count": 5}
{"type": "invest", "house": 0, "science": "alchemy", "books": 500}
{"type": "set_science", "house": 0, "focus": "military", "paper_budget": 20}
{"type": "assign_scientists", "house": 0, "from": "economy", "to": "arcane", "count": 1}
{"type": "set_research", "house": 0, "project": "statesmanship"}
{"type": "contribute", "house": 0, "category": "economy", "books": 1000}
{"type": "spy", "house": 0, "target": 5, "thieves": 20}
{"type": "operation", "house": 0, "target": 5, "op": "survey", "thieves": 20}
{"type": "leave_protection", "house": 0}
{"type": "cast", "house": 0, "rite": "war_hymns"}
{"type": "cast", "house": 0, "rite": "rot", "target": 5}
{"type": "operation", "house": 0, "target": 5, "op": "strongbox", "thieves": 50, "frame": [6, 2]}
{"type": "reassure", "house": 0, "character": {"kind": "general", "id": 3}}
{"type": "open_vigil", "house": 0, "vigil": "walls"}
{"type": "give_to_vigil", "house": 0, "aether": 500, "incense": 10}
{"type": "operation", "house": 0, "target": 5, "op": "smuggle", "thieves": 200, "material": "amber"}
{"type": "operation", "house": 0, "target": 5, "op": "pilfer", "thieves": 100, "category": "military"}
{"type": "operation", "house": 0, "target": 5, "op": "fires", "thieves": 100, "building": "homes"}
{"type": "vote", "house": 0, "vote": {"type": "for", "house": 3}}
{"type": "vote", "house": 0, "vote": {"type": "against"}}
{"type": "order", "house": 0, "side": "sell", "material": "bauxite", "quantity": 50, "price": 10}
{"type": "order", "house": 0, "treasury": true, "side": "buy", "material": "paper", "quantity": 20, "price": 12}
{"type": "cancel", "house": 0, "order": 7}
{"type": "donate", "house": 0, "material": "bauxite", "amount": 25}
{"type": "grant", "house": 0, "to": 3, "amount": 1000}
{"type": "set_tax", "house": 0, "rate_bp": 300}
{"type": "answer_quest", "house": 0, "commit": true}
{"type": "propose_work", "house": 0}
{"type": "vote_work", "house": 0, "yes": true}
{"type": "fund_work", "house": 0, "gold": 100000, "material": 200}
{"type": "rename_state", "house": 0, "name": "The Ashen Reach"}
{"type": "mirror", "house": 0, "of": 12}
{"type": "second_strike", "house": 0}
{"type": "retake_land", "house": 0, "from": 12, "acres": 5}
{"type": "send_aid", "house": 0, "to": 3, "what": "gold", "amount": 5000}
{"type": "recruit_academic", "house": 0, "name": "Orla", "category": "economy"}
{"type": "list", "house": 0, "character": {"kind": "general", "id": 1}, "reserve": 5000, "ticks": 24}
{"type": "list", "house": 0, "character": {"kind": "academic", "id": 2}}
{"type": "delist", "house": 0, "listing": 4}
{"type": "bid", "house": 0, "listing": 4, "price": 6000}
{"type": "make_offer", "house": 0, "character": {"kind": "general", "id": 9}, "price": 8000}
{"type": "accept_offer", "house": 0, "offer": 3}
{"type": "drop_offer", "house": 0, "offer": 3}
{"type": "mark_heirs", "house": 0, "heirs": [{"kind": "general", "id": 1}]}
{"type": "declare_war", "house": 0, "realm": 6, "state": 3}
{"type": "offer_peace", "house": 0}
{"type": "withdraw", "house": 0}
{"type": "propose_ceasefire", "house": 0, "realm": 6, "state": 3, "ticks": 48}
{"type": "break_ceasefire", "house": 0, "realm": 6, "state": 3}

Leave out material in donate and grant to move gold. treasury orders, grant, set_tax, rename_state, set_research and the war commands, propose_work and vote_work are for the state leader only. propose_work puts forward your realm's work (your own state votes yes); any house in the realm may fund_work it once it has passed, with gold, the realm's material or both, each capped at what is still needed. A list without reserve and ticks takes offers instead of running an auction. Rates are in basis points: 300 means 3%.

An order fills at once against waiting orders (see Trade) and answers {"type": "order_placed", "order": 12, "filled": 100, "gold": 777, "fee": 36, "cross_fee": 21, "left": 20}: units filled at once, gold paid (a buy, fees included) or received (a sale, after the fee), the market fee, the cross-realm fee a buy paid (0 if every seller was in its realm), and units left waiting in the book. The fee rate is params.cross_realm_fee_bp in /rules; a buy holds room for it. News items order_filled carry fee and cross_fee for each fill, and treasury_share (materials: units by material; ticks: over how many) tells a house once a day what it got from its treasury's overflow. A cancel within 10 minutes of placing is refused.

An operation or cast answers with chance: how good its chance was, as a band (very low, low, even, good, very good, or sure for a rite on yourself). The band edges are params.chance_bands_bp in /rules. The exact number isn't sent, because it would reveal the target's hidden thieves, adepts, Vigilance and Ward. With intel (an intel operation, spy or a divination) the answer also carries hit: {"battered_tenths", "protection_bp"}, the target's battered level in tenths and the share of the usual gains an attack on it yields now; the stored report carries it too. A make_offer is only for a listed character.

Races and personalities are named by identity. units uses the slots above. medics, general, horses, chariots and kind (an id from Attacks; default assault) are optional in an attack. So are upgraded_mercenaries, how many of the hired mercenaries fight upgraded (races with mercenary upgrades only), and double_strike, to keep a second strike ready for second_strike (races with a double strike, at war, led by a general with enough traits). Give both realm and state to ask for a state, or neither (see placement).

Before the season starts, every command except create_house and POST /heirs/recall is refused: "the season hasn't started yet".

Staff

Staff run a live game through /dev/* requests (see Running the Next Age). GET /dev says whether you're staff and what you may do (can). The age requests:

RequestWhat it does
GET /dev/ageThis age, the planned next age with the game's check of it (plan_check), the age files to choose from (rulesets) and the lowest number the next age may take (admin)
POST /dev/age/planPlan the next age: {"ruleset", "number", "name", "start_at", "params", "note"}; checked by compiling, with every problem listed in errors (admin). DELETE drops the plan
POST /dev/age/endEnd the age now: {"note"} (owner)
POST /dev/age/openOpen the planned age once this one has ended: the world is archived and the server restarts into the new one (owner)

Answers

  • 200: done; the body says what happened (for example {"type": "created", "house": 0, "realm": 4, "state": 2, "seat": 1}). A house that took a robot's seat in a full state also gets replaced: the id of the robot that left. It also carries after: your house's resources right after the command (id, gold, food, peasants, units and away by slot, training, medics, horses, chariots, aether, adepts, land, incoming_land, explorable_acres, materials), so a page can show what is left at once rather than wait for /me, which may lag by a quarter of a second or more.
  • 401: sign in first (see Signing In). Orders use your sign-in cookie, or a connected site's Authorization: Bearer token.
  • 422: the game refused the order; error says why (for example "that house isn't yours").
  • 429: too many requests. Orders allow a burst of 30, then 5 a second. Chat has its own limit: a burst of 5, then one message every 2 seconds (see Chat).

Reads may lag the game by up to a quarter of a second. What each request may show follows What You Can See: another house's hidden numbers show only in a report, from a successful intel operation (spy is the same as the dossier operation) or a divination.

On this page
  1. Connecting from another site
  2. Commands
  3. Staff
  4. Answers