Docs API Reference

API Reference

Complete documentation for every function in the MAS Python SDK.

Note: Device connection is handled automatically by MAS. Scripts do not need to explicitly connect or disconnect - the device is pre-configured when your script runs.

Namespace Map

Deviceget_screen_size, get_device_info, get_host_machine_id
Interactionclick, swipe, input_text, key_press
Visiontake_screenshot, find_object, find_objects, find_any_object, wait_for_object, read_text
Image Libraryimages()
Appsopen_app, close_app, get_current_app, get_app_state, is_app_focused
Storagesave, retrieve, retrieve_all, clear, get_current_device_port
Utilitiesget_clipboard
Runtime UImas.ui.set_text, set_progress, add_data_point, append_text, set_table_data, on_click, batch, ...

Types

All types are importable from mas (e.g. from mas import Region, KeyCode).

Region

dataclass

A rectangular region on screen defined by corner coordinates.

from mas import Region
region = Region(x1=100, y1=200, x2=900, y2=260)
FieldTypeDescription
x1intLeft edge x-coordinate (pixels from left)
y1intTop edge y-coordinate (pixels from top)
x2intRight edge x-coordinate (pixels from left)
y2intBottom edge y-coordinate (pixels from top)

Coordinates

type alias

An (x, y) tuple. swipe takes these; ObjectMatch.center returns one.

Screenshot

dataclass

Result of a screenshot capture operation.

FieldTypeDescription
base64strBase64-encoded PNG image data
widthintScreenshot width in pixels
heightintScreenshot height in pixels
timestampstrCapture timestamp in ISO 8601 format

ObjectMatch

dataclass

Result of a template matching operation. x and y are the center of the match.

FieldTypeDescription
xintCenter x coordinate
yintCenter y coordinate
centertuple[int, int]Property returning (x, y) — use as mas.click(*match.center)
matched_template_idintImage ID that matched (relevant after find_any_object)

TextRecognitionResult

dataclass

Result of OCR text recognition.

FieldTypeDescription
textstrExtracted text
confidencefloatRecognition confidence (0.0 to 1.0)
regionRegionRegion where text was found

ScreenSize

dataclass

width: int

height: int

DeviceInfo

dataclass

id, name: str

type: "android" | "desktop"

screen_width, screen_height: int

connected: bool

KeyCode

enum

Android hardware key codes for key_press.

BACK
HOME
MENU
APP_SWITCH
ENTER
DELETE
SPACE
TAB
POWER
CAMERA
VOLUME_UP
VOLUME_DOWN
BRIGHTNESS_UP
BRIGHTNESS_DOWN
DPAD_UP
DPAD_DOWN
DPAD_LEFT
DPAD_RIGHT
DPAD_CENTER
MEDIA_PLAY
MEDIA_PAUSE
MEDIA_NEXT
MEDIA_PREVIOUS

+ NUM_0NUM_9 for the numeric keypad.

Modifier

enum

SHIFT

CTRL

ALT

META

ColorConversion

enum

NONE

BGR_TO_GRAY

RGB_TO_GRAY

+ HSV / RGB variants for special cases.

SearchStrategy

literal

"first_match"

"best_match"

"priority_order"

ImageMap / ImageRef

returned by mas.images()

An ImageMap is a read-only object with one ImageRef per declared image, reached by dot access. Any vision call accepts an ImageRef directly. See the Image Library section.

FieldTypeDescription
idintThe Image Library ID
namestrThe attribute name from the mas.images() mapping

UIEvent

returned by mas.ui.wait_for_event

An event fired by a runtime dashboard widget the user interacted with.

FieldTypeDescription
widget_idstrThe widget's name from the .uibrt
event_typestr"click" or "change"
valueAnyCurrent widget value (for input changes)

Device Control

mas.get_screen_size() ScreenSize

Get the current device screen dimensions in pixels. Use it to compute coordinates so the macro survives a resolution change.

size = mas.get_screen_size()
mas.click(size.width // 2, size.height // 2) # tap center

mas.get_device_info() DeviceInfo

Get detailed metadata about the connected device.

info = mas.get_device_info()
print(f"{info.name}{info.screen_width}x{info.screen_height}")

mas.get_host_machine_id() str

A stable UUID for the PC the macro runs on — the same across restarts, shared by every macro on that machine. It is the default machine_id for storage, so you rarely call it directly. Raises RPCError rather than returning an empty string.

machine_id = mas.get_host_machine_id()

User Interaction

mas.click(x, y, delay_ms=1000) None

Tap at pixel (x, y). delay_ms is a pause after the tap — useful for letting a screen settle. Default 1000 ms; drop to 0 in tight loops.

ParamTypeDefaultDescription
xintrequiredHorizontal position (pixels from left)
yintrequiredVertical position (pixels from top)
delay_msint1000Delay after click in milliseconds
mas.click(540, 1200)
mas.click(540, 1200, delay_ms=0) # no pause

mas.swipe(from_coords, to_coords, duration_ms=1000) None

Drag from one point to another. Both points are (x, y) tuples. duration_ms controls speed: short for a flick, long for a controlled drag.

ParamTypeDefaultDescription
from_coordstuple[int, int]requiredStarting (x, y) coordinates
to_coordstuple[int, int]requiredEnding (x, y) coordinates
duration_msint1000Swipe duration in milliseconds
mas.swipe((540, 1600), (540, 600), duration_ms=400) # fast scroll up

mas.input_text(text, delay_ms=0) None

Type into the currently focused field. Tap the field first to focus it.

mas.click(300, 450) # focus the field
mas.input_text("user@example.com")

mas.key_press(key_code, modifiers=None, duration_ms=100) None

Press a hardware key. duration_ms is the hold time — raise it for a long-press.

ParamTypeDefaultDescription
key_codeKeyCoderequiredKey to press
modifierslist[Modifier] | NoneNoneOptional modifier keys
duration_msint100Hold time
from mas import KeyCode
mas.key_press(KeyCode.BACK)
mas.key_press(KeyCode.POWER, duration_ms=3000) # long-press

Computer Vision

Macros decide what to do by looking at the screen. You give the SDK a template image (registered in the Image Library) and it finds where that image appears on the device.

mas.take_screenshot() Screenshot

Capture the screen as an in-memory base64 PNG. The main reason to call this explicitly is to reuse one screenshot across several searches and avoid re-capturing for each.

shot = mas.take_screenshot()
play = mas.find_object(images.play, screenshot=shot)
coins = mas.find_object(images.coins, screenshot=shot)

mas.find_object(image_id, *, threshold=0.8, ...) ObjectMatch | None

Find one template on screen. Returns an ObjectMatch or None if nothing matched at or above threshold.

ParamTypeDefaultDescription
image_idint | ImageRefrequiredImage Library ID or ImageRef
thresholdfloat0.8Match confidence 0.0–1.0
screenshotScreenshot | NoneNoneReuse a captured frame
continuous_modeboolFalseRetry until found or timeout_ms
timeout_msint1000Budget for continuous_mode
capture_interval_msint500Gap between retries
max_matchesint1Max matches to return
search_regionRegion | NoneNoneRestrict the search; faster and fewer false hits
match = mas.find_object(images.login_btn)
if match: mas.click(*match.center)

# only search the top-left quadrant
mas.find_object(images.badge, search_region=Region(0, 0, 500, 400))

mas.find_objects(image_id, *, max_matches=10, ...) list[ObjectMatch]

Same parameters as find_object but returns every match, sorted best-confidence first. Default max_matches is 10.

for star in mas.find_objects(images.collectible, max_matches=20):
mas.click(*star.center)

mas.find_any_object(image_ids, *, search_strategy="first_match", ...) ObjectMatch | None

Search for several templates at once — handy when a UI element has variants (themes, languages, states). Takes a list. The returned match's .matched_template_id tells you which one hit.

StrategyBehavior
"first_match"Return the first template that matches (fastest)
"best_match"Try all, return the highest-confidence hit
"priority_order"Prefer earlier templates in the list
match = mas.find_any_object(
[images.claim_en, images.claim_fr, images.claim_es],
search_strategy="best_match",
)

mas.wait_for_object(image_id, timeout_ms=10000, threshold=0.8) ObjectMatch | None

Block until a template appears, or timeout_ms elapses. The wait happens server-side, so it is more efficient than polling find_object yourself. The right call for "do something, then wait for the next screen".

mas.click(*login.center)
home = mas.wait_for_object(images.home_screen, timeout_ms=30000)
if home is None: raise RuntimeError("home never loaded")

mas.read_text(region=None, screenshot=None, model="eng_best", psm=7, color_conversion=ColorConversion.NONE, timeout_ms=30000) TextRecognitionResult

OCR — read text off the screen. Always pass a region; OCR on a tight box is faster and far more accurate than reading the whole screen.

ParamTypeDefaultDescription
regionRegion | NoneNoneArea to read — omit to read the whole screen (slow)
screenshotScreenshot | NoneNoneReuse a frame instead of capturing
modelstr"eng_best""eng_best" (accurate), "eng_fast", "eng"
psmint7Page-segmentation mode (see below)
color_conversionColorConversionNONEPreprocessing step (try BGR_TO_GRAY on busy backgrounds)
timeout_msint30000OCR time budget

psm — tells the engine the shape of the text: 7 single line (default), 8 single word, 6 uniform block, 11 scattered text.

from mas import Region, ColorConversion

result = mas.read_text(region=Region(420, 90, 660, 140))
score = int(result.text) if result.text.strip().isdigit() else 0

# single word on a noisy background
word = mas.read_text(region=Region(100, 200, 400, 260),
psm=8, color_conversion=ColorConversion.BGR_TO_GRAY)

Image Library

Template images do not live in your macro folder. They live in the Image Library inside Macro Automation Studio, where each image has a numeric ID. Vision calls take that ID.

You could pass raw integers, but that scatters meaningless numbers through your code. Declare them once with mas.images() and refer to them by name.

mas.images(mapping) ImageMap

Declare your images by name. Returns an ImageMap — a read-only object with one ImageRef per entry. Names must be valid Python identifiers.

import mas

images = mas.images({
"login_btn": 123,
"home_screen": 456,
"daily_reward": 789,
})

match = mas.find_object(images.login_btn)

Put all your macro's images in one mas.images({...}) call near the top of your script. Studio uses that call to know which images to bundle when the macro is published.

App Management

mas.open_app(package_name, timeout_ms=2000) None

Launch an Android app by package name.

mas.open_app("com.instagram.android")
mas.open_app("com.android.chrome", timeout_ms=5000)

mas.close_app(package_name) None

Force-stop an app.

mas.get_current_app() str

Package name of whatever is in the foreground.

mas.get_app_state(package_name) int

How the app is currently running. Compare against the module constants:

ConstantValueMeaning
mas.RUNNING_IN_FOREGROUND4Visible and focused
mas.RUNNING_IN_BACKGROUND3Running, not visible
mas.RUNNING_IN_BACKGROUND_SUSPENDED2Backgrounded and frozen
mas.NOT_RUNNING1Installed, not running
mas.NOT_INSTALLED0Not on the device
if mas.get_app_state("com.example.game") == mas.NOT_RUNNING:
mas.open_app("com.example.game")

mas.is_app_focused(package_name) bool

True if that app currently has input focus. Good for guarding a tap or typing.

mas.open_app("com.example.game")
while not mas.is_app_focused("com.example.game"):
time.sleep(0.5)

Persistent Storage

mas.save / mas.retrieve give a macro memory across runs — a streak counter, a checkpoint, a "last processed" marker. Data is any JSON-serializable dict.

Every entry is keyed by the triple (machine_id, port, task_name). Leave machine_id and port at their defaults — they handle the common case (per-PC, per-device-port state) automatically.

mas.save(task_name, data, machine_id=None, port=0) bool

Persist a dict. Re-saving the same key updates the entry. Raises ValueError if data is not JSON-serializable.

mas.save("daily_login", {"streak": 7, "last_run": "2026-05-17"})

mas.retrieve(task_name, machine_id=None, port=0) dict

Read an entry back, or an empty dict if nothing was stored. The load-or-default pattern is one line.

state = mas.retrieve("daily_login") # {} on first run
streak = state.get("streak", 0) + 1
mas.save("daily_login", {"streak": streak})

mas.retrieve_all(task_name) list[dict]

Every entry for a task name, across all machines and ports. Each item is { "machine_id", "port", "data", "updated_at" }. Use it to roll up results from many emulators running the same macro.

total = sum(e["data"].get("collected", 0) for e in mas.retrieve_all("harvest"))

mas.clear(task_name, ...)

Delete a stored entry.

mas.get_current_device_port()

The current device's port (e.g. 5556). Useful for labelling per-device output.

Utilities

mas.get_clipboard() str

Read the device clipboard as text.

Reading Argument-Form Values

If your macro has a .uibproj argument form, the UI Builder generated src/script_args.py. Import it to read the user's inputs, organized by the form's tabs.

from src.script_args import args

target_url = args.general.target_url # tab "General", key "target_url"
max_items = args.general.max_items # Number Input → int
headless = args.advanced.headless # Checkbox → bool

script_args.py parses the command line at import time, so args is ready to use immediately. It is generated — regenerate it from the UI Builder whenever the form changes; do not hand-edit it.

Runtime UI — mas.ui.*

The mas.ui.* namespace drives a runtime dashboard — the live panel shown next to the device while your macro runs. You design the dashboard in the UI Builder (a .uibrt file), give every widget a name, then drive each one by that name from your script.

A name typo between the .uibrt and the call is the most common dashboard bug — the call simply updates nothing.

Widget → Method Map

To update…Call
a Runtime Labelmas.ui.set_text(name, text)
a Progress Barmas.ui.set_progress(name, value)
a Chart (stream)mas.ui.add_data_point(name, value, label, series=None)
a Chart (replace)mas.ui.set_chart_data(name, data) · clear_chart(name)
a Text Area (append)mas.ui.append_text(name, line)
a Text Area (replace)mas.ui.set_textarea(name, text) · clear_text(name)
a Tablemas.ui.set_table_data · append_table_row · clear_table
read a Buttonmas.ui.wait_for_event() · on_click(name, handler)
read a Text Inputmas.ui.get_input_value(name) · on_change(name, handler)

Display widgets — the script writes, the user reads

mas.ui.set_text(name, text)

Update a Runtime Label, the caption of a progress bar, a text area, or a button label.

mas.ui.set_text("status_label", "Logging in…")

mas.ui.set_progress(name, value)

Update a Progress Bar. Studio clamps value to the widget's [min, max].

mas.ui.set_progress("download_bar", 40)

mas.ui.add_data_point · set_chart_data · clear_chart

A chart holds a series of { label, value } points. Stream them as they arrive or replace the dataset at once. series groups points into named lines/bars.

# streaming
mas.ui.add_data_point("cpu_chart", value=cpu, label=f"t={t}", series="CPU")

# replace
mas.ui.set_chart_data("totals", [{"label": "Gold", "value": 1250}])

mas.ui.append_text · set_textarea · clear_text

A text area is the natural place for a running log. append_text adds a line and auto-scrolls; lines past maxLines drop off the top.

mas.ui.append_text("log", "Run started")
mas.ui.set_textarea("log", "fresh contents")

mas.ui.set_table_data · append_table_row · clear_table

A table has fixed columns (set in the UI Builder). Rows are dicts whose keys match those column names. Use mas.ui.cell_button(...) inside a cell to get a per-row button.

mas.ui.set_table_data("results", [
{"Item": "Gold", "Count": "1,250", "Status": "OK"},
])
mas.ui.append_table_row("results", {"Item": "Stone", "Count": "12", "Status": "low"})

Interactive widgets — the user acts, the script reads

Two models: blocking (the script waits for the user) and callback (handlers fire on a background thread while the script keeps working). Pick one per macro and stay with it.

mas.ui.wait_for_event(timeout=None) UIEvent | None

Model A — blocking. Your script stops and waits for the user. Returns a UIEvent or None on timeout.

event = mas.ui.wait_for_event(timeout=60)
if event and event.widget_id == "start_btn":
begin_run()

mas.ui.on_click · on_change · start_listener

Model B — callback. Register handlers, start a background listener, and keep working. The handlers fire on a daemon thread. This is the right model for a Stop or Pause button that must be reactive during a long task.

stop = False
def on_stop(): global stop; stop = True

mas.ui.on_click("stop_btn", on_stop)
mas.ui.start_listener() # register handlers BEFORE this

for i in range(1000):
if stop: break
do_work(i)

start_listener() is idempotent and returns immediately. The listener runs on its own RPC connection on a daemon thread. A handler that raises is caught and logged.

mas.ui.get_input_value(name) str | None

Read the current value of a Text Input on demand — no listener required.

query = mas.ui.get_input_value("search_box")

with mas.ui.batch(): ... context manager

Each mas.ui.* call is one round-trip to Studio. Wrap multiple widget updates in batch() to send them as one call — the difference between a smooth dashboard and a flickering one.

with mas.ui.batch():
mas.ui.set_text("status", f"Step {i}/{n}")
mas.ui.set_progress("main_bar", i * 100 // n)
mas.ui.add_data_point("rate", value=rate, label=str(i))

batch() blocks cannot be nested. The buffer is process-global — finish the with before letting listener callbacks fire their own updates.

Exceptions

Rule of thumb: None means "not found, that's fine" — handle it with if. An exception means "something is wrong" — let it stop the macro unless you have a real recovery.

Every SDK error descends from MASError. RPC-level failures are RPCError (with .code, .message, .data).

MASError (base)
|
RPCError
|-- ConnectionError could not reach Studio
|-- ParseError (-32700)
|-- InvalidRequestError (-32600)
|-- MethodNotFoundError (-32601)
|-- InvalidParamsError (-32602)
|-- InternalError (-32603)
|-- DeviceNotConnectedError (-32001)
|-- DeviceNotFoundError (-32002)
|-- CommandFailedError (-32003)
|-- ImageNotFoundError (-32004)
|-- TimeoutError (-32005)
try:
mas.open_app("com.example.game")
home = mas.wait_for_object(images.home_screen, timeout_ms=30000)
if home is None: raise RuntimeError("home never loaded")
except mas.ImageNotFoundError as e:
print(f"image missing: {e}"); raise
except mas.DeviceNotConnectedError:
print("lost the device"); raise