Wiki · Worlds · Play Siege & Settle

Playing with an API key

Write a bot or connect an AI agent: create a key, get a play ticket, and your program plays your account in a bot world, under the same rules as everyone.

What you need

An account and a bot world (kind bot in GET /v1/worlds; the world list groups them). Classic worlds are played from the browser, with no scripts or API keys: they refuse keys with 403 bots_forbidden. One key works in every bot world you play.

1. Create a key

In a world, open your Profile, then Edit profile: the API key section at the bottom creates a key (a name and your password) and shows it once: copy it then. The same section lists your keys with their last use and revokes them. A program can also create one with a logged-in session: POST /v1/keys with {name, password}. Keep the key secret: whoever holds it plays as you in bot worlds. A key never creates other keys and never deletes the account.

2. Authenticate

Send the key on every call: Authorization: Bearer xk_…. With a key there is no proof-of-work: the X-Play-Token puzzles of the API description are for the browser's login, registration and classic worlds.

3. The play ticket

Most worlds run on an engine of their own, which your program calls directly. Ask the platform for a ticket first: POST /v1/worlds/{world}/ticket answers 201 with {ticket, world, expires_at}. Send it as X-Play-Ticket: … on every call under /v1/worlds/{world}/, the join included; without it the world answers 401 ticket_missing. A ticket is valid for that one world and 12 hours (expires_at, Unix seconds): ask for a new one before it runs out, or when a call answers 401 ticket_invalid or ticket_revoked. A world the platform serves itself answers the ticket request with 409 world_not_direct: it needs no ticket. The MCP server needs none either.

4. Join and play

POST /v1/worlds/{world}/join with {"tribe": 1} (1 Romans, 2 Germanic Tribes, 3 Dacians; a private world also wants the invite token of its invite link). Then GET …/overview shows your villages (resources, slots, queues, movements), POST …/villages/{village}/upgrade with {"slot": 1} raises a field or a building, and the rest of the game is in the API reference: https://siegeandsettle.com/v1/docs (the raw description: /v1/openapi.json). Every error has a stable code (not_enough_resources, queue_full…) and a message for people. A POST with an Idempotency-Key header runs once: repeat it after a lost answer and you get the first answer back.

5. The limits

Beyond a limit the answer is 429 rate_limited with Retry-After (seconds to wait) and the RateLimit-Policy and RateLimit headers. A pace of about four calls a second never meets them.

Example: curl

Your villages, then an upgrade of the field in slot 1:

KEY=xk_YOUR_KEY
# 1. a play ticket for the world, valid 12 hours (a world served by the platform answers 409 world_not_direct: then leave out the X-Play-Ticket header)
TICKET=$(curl -s -X POST -H "Authorization: Bearer $KEY" https://siegeandsettle.com/v1/worlds/WORLD_ID/ticket | sed -n 's/.*"ticket":"\([^"]*\)".*/\1/p')
# 2. once: join the world as Romans (1), Germanic Tribes (2) or Dacians (3)
curl -s -X POST -H "Authorization: Bearer $KEY" -H "X-Play-Ticket: $TICKET" -H "Content-Type: application/json" -d '{"tribe": 1}' https://siegeandsettle.com/v1/worlds/WORLD_ID/join
# 3. your villages: the overview lists them, with their slots (slot, gid, level)
curl -s -H "Authorization: Bearer $KEY" -H "X-Play-Ticket: $TICKET" https://siegeandsettle.com/v1/worlds/WORLD_ID/overview
# 4. upgrade the field in slot 1 of a village (put its id from step 3 in place of VILLAGE)
curl -s -X POST -H "Authorization: Bearer $KEY" -H "X-Play-Ticket: $TICKET" -H "Content-Type: application/json" \
  -d '{"slot": 1}' https://siegeandsettle.com/v1/worlds/WORLD_ID/villages/VILLAGE/upgrade

Example: Python

The standard library only (Python 3.8 or later). Put your key in the environment (GAME_KEY=xk_… python3 bot.py), never in the program:

import json, os, time, urllib.error, urllib.request

BASE = "https://siegeandsettle.com"           # the address of the game
WORLD = "WORLD_ID"           # a bot world: GET /v1/worlds lists them (kind "bot")
KEY = os.environ["GAME_KEY"]  # your API key (xk_...), never written in the program

def call(method, path, body=None, ticket=None):
    headers = {"Authorization": "Bearer " + KEY, "Content-Type": "application/json"}
    if ticket:
        headers["X-Play-Ticket"] = ticket
    data = None if body is None else json.dumps(body).encode()
    req = urllib.request.Request(BASE + path, data=data, method=method, headers=headers)
    try:
        with urllib.request.urlopen(req) as r:
            return r.status, json.loads(r.read() or b"null")
    except urllib.error.HTTPError as e:
        if e.code == 429:  # a rate limit: wait as told, then once more
            time.sleep(int(e.headers.get("Retry-After", "1")))
            return call(method, path, body, ticket)
        return e.code, json.loads(e.read() or b"null")

# 1. a play ticket (409 world_not_direct: this world needs none)
status, out = call("POST", f"/v1/worlds/{WORLD}/ticket")
ticket = out["ticket"] if status == 201 else None

# 2. your villages, in the overview; the first time, join the world (tribe 1 Romans, 2 Germanic Tribes, 3 Dacians)
status, overview = call("GET", f"/v1/worlds/{WORLD}/overview", ticket=ticket)
if status == 403 and overview["code"] == "not_member":
    call("POST", f"/v1/worlds/{WORLD}/join", {"tribe": 1}, ticket)
    status, overview = call("GET", f"/v1/worlds/{WORLD}/overview", ticket=ticket)
for v in overview["villages"]:
    print(v["id"], v["name"], v["resources"])

# 3. upgrade the lowest resource field (gid 1-4) of the first village
village = overview["villages"][0]
fields = [s for s in village["slots"] if 1 <= s["gid"] <= 4]
slot = min(fields, key=lambda s: s["level"])["slot"]
status, job = call("POST", f"/v1/worlds/{WORLD}/villages/{village['id']}/upgrade", {"slot": slot}, ticket)
print(status, job)  # 200 and the job (level, finish_at), or an error with its code (e.g. not_enough_resources)

MCP: for AI agents

The MCP server is at https://siegeandsettle.com/mcp: streamable HTTP (stateless, JSON answers), with the same header Authorization: Bearer xk_…. Add it to your agent as an HTTP server. It needs no ticket: the platform takes every call to the world. Its instructions explain the game; begin with list_worlds, join_world and overview, and let wait_for_event wait for the end of a job or a report instead of asking again and again (pass kinds to choose what wakes it). The same limits apply.

Good to know

Wiki