API tour

Python · Updated 09 November 2026

The context, game state, actions, inventories, entity queries, walking and events.

This tour builds on the framework's example script, which reports state, counts nearby NPCs and walks into Lumbridge Castle, going through its door on the way:

from botwithus.api import (
    BotScript, Npcs, ScriptContext, Tile, bool_field, int_field, script_manifest)


@script_manifest(name="Example", version="1.1", author="botwithus",
                 description="Reports state and walks into Lumbridge Castle.", category="Other")
class Example(BotScript):
    GOAL = Tile(3208, 3220, 0)

    def get_config_fields(self):
        return [
            int_field("radius", "NPC count radius", 8, min=1, max=32,
                      description="How far around the player to count NPCs, in tiles"),
            bool_field("walk", "Walk into the castle", True),
        ]

    def on_config_update(self, config: dict) -> None:
        self.radius = config["radius"]
        self.walk = config["walk"]

    def on_start(self, ctx: ScriptContext) -> None:
        self.ctx = ctx
        self.arrived = False

    def on_loop(self) -> int:
        snap = self.ctx.snapshot()
        me = snap.self_player
        if not me.has_tile:
            return 1000

        near = Npcs(snap).within(me.tile, self.radius).count()
        self.ctx.log.info("tick=%s tile=%s npcs<=%s=%s", snap.server_tick, me.tile,
                          self.radius, near)

        nav = self.ctx.navigation
        if self.walk and nav is not None and not self.arrived:
            self.arrived = nav.walk(self.GOAL, radius=2)
            if self.arrived:
                self.ctx.log.info("arrived at goal")

        if not self.ctx.sleep_ticks(1):
            return -1
        return 0

    def on_stop(self) -> None:
        self.ctx.log.info("stopped")

The context

Attribute What it is
ctx.snapshot() the current game state
ctx.self_player your player, from the current state
ctx.game GameApi: actions and live reads
ctx.navigation SceneWalker: walking (can be None if navigation is off)
ctx.events EventBus: game events
ctx.log the script's logger
ctx.config the current settings, read-only

Your player

me.has_tile is the check to make before anything that needs your position. A player can be valid and still have no tile for a moment, for example while the game moves it in the scene. me.tile is a Tile(x, y, plane). Other fields include animation_id, moving, combat_level and skills.

Finding things

Npcs, Players, SceneObjects and GroundItems turn the snapshot into a query:

near = Npcs(snap).within(me.tile, self.radius).count()

A query supports where(predicate), within(tile, radius), all(), count(), first() and nearest(tile). Scenery entries have a type_id, so you can pick out one kind of object with where:

tree = SceneObjects(snap).where(lambda loc: loc.type_id == TREE_ID).nearest(me.tile)

Inventories

backpack(snap), bank(snap) and equipment(snap) give a read-only view with contains(item_id), count(item_id), free_slots(), is_full(), is_empty() and first(item_id).

Acting

Actions are built by small functions in botwithus.api and queued with ctx.game.queue_action(...):

  • object_action(loc, slot): right-click option slot (1 to 6) on a scene object;
  • npc_action(npc, slot): the same on an NPC;
  • ground_item_action(item, slot): the same on a ground item;
  • backpack_action(slot, option): click backpack slot slot (counted from 0) with menu option option (counted from 1);
  • walk_to(x, y), component_click(...), dialogue_option(...).
if tree is not None:
    self.ctx.game.queue_action(object_action(tree, 1))

queue_actions([...]) sends several actions at once. The game queues all of them or none.

backpack_action has no default option on purpose. The position of "Drop" in an item's menu depends on the item.

Walking

ctx.navigation offers two styles:

  • walk(goal) is a blocking full walk. It plans the route, walks it and carries out its transitions (doors, stairs, teleports, dialogue), then returns True when it arrives. The example uses it, because the castle is behind a door. walk_ex(goal) does the same and returns a detailed result.
  • walk_to(goal) plans and queues one step towards goal, then returns whether you have arrived. Call it once per loop. It walks only up to doors, stairs and other transitions, so it cannot get through a closed door on its own.

Both need a position to start from. Check ctx.self_player.has_tile first: walk_to and walk return False without one, and walk_ex raises.

Events

ctx.events.subscribe(EventType, handler) registers a handler and returns a function that unsubscribes it. Event types include ChatMessage, VarpChanged, VarbitChanged, ActionExecuted, ActionRefused, BreakStarted and BreakEnded.

Breaks

ctx.game.schedule_break(300) holds the action queue for 300 seconds. Fractions are allowed. ctx.game.interrupt_break() ends a break early.

Varps and varbits

Read them with ctx.game.read_varp(id) / read_varbit(id). Each read carries a state (SET, DEFAULT_NOT_SET_CLIENTSIDE, NO_SUCH_VARP, UNAVAILABLE) beside its value. Decide on the state, not the value alone: a value of -1 can mean "set to -1" or "not set". In the lobby and for the first few ticks in the world, every read is UNAVAILABLE.

Next: Logs and crash files.