Official Python SDK for Qirabot - AI-powered device automation platform.
Automate mobile and web devices with natural language or structured actions. Let AI see the screen, click, type, extract data, and verify results.
pip install qirabotRequires Python 3.10+.
Step-by-step control with real-time feedback via WebSocket:
from qirabot import Qirabot
bot = Qirabot("qk_your_api_key", base_url="https://app.qirabot.com")
with bot.task("device-id", name="wiki-extract") as t:
t.navigate("https://en.wikipedia.org")
t.type("Search Wikipedia input", "Artificial intelligence")
t.click("Search button")
t.wait_for("Wikipedia article page is visible", timeout_ms=10000)
summary = t.extract("Get the first paragraph of the article")
print(f"Summary: {summary}")
t.verify("The article title contains 'Artificial intelligence'")Fire-and-forget execution with polling:
from qirabot import Qirabot, Action
bot = Qirabot("qk_your_api_key")
# Single AI instruction — let AI handle the entire workflow
task_id = bot.submit(
"device-id",
name="hn-top-stories",
instruction="Go to news.ycombinator.com, extract the top 3 story titles and their scores",
)
result = bot.wait(task_id, timeout=120)
print(f"Status: {result.status}, Steps: {len(result.steps)}")
# Composed actions — precise control over each step
task_id = bot.submit("device-id", name="github-trending", actions=[
Action.navigate("https://github.com/trending"),
Action.wait_for("Trending repositories page is loaded"),
Action.extract("Get the names and descriptions of the top 5 trending repositories", variable="repos"),
Action.take_screenshot(),
])
result = bot.wait(task_id)
for step in result.steps:
if step.output:
print(step.output)Download screenshots from completed tasks:
# Download a single step screenshot
bot.download_screenshot(task_id, step=1, path="step_1.png")
# Get screenshot bytes without saving to file
img_bytes = bot.download_screenshot(task_id, step=1)
# Download all screenshots as a ZIP archive
bot.download_images(task_id, path="task_images.zip")| Action | Android | iOS | Chrome | Desktop | Sandbox |
|---|---|---|---|---|---|
click |
✓ | ✓ | ✓ | ✓ | ✓ |
double_click |
✓ | ✓ | ✓ | ✓ | ✓ |
type_text |
✓ | ✓ | ✓ | ✓ | ✓ |
type_direct |
✓ | ✓ | ✓ | ✓ | ✓ |
clear_text |
✓ | ✓ | ✓ | ✓ | ✓ |
press_key |
✓ | ✓ | ✓ | ✓ | ✓ |
scroll |
✓ | ✓ | ✓ | ✓ | ✓ |
scroll_at |
✓ | ✓ | ✓ | ✓ | ✓ |
wait |
✓ | ✓ | ✓ | ✓ | ✓ |
wait_for |
✓ | ✓ | ✓ | ✓ | ✓ |
take_screenshot |
✓ | ✓ | ✓ | ✓ | ✓ |
ai |
✓ | ✓ | ✓ | ✓ | ✓ |
extract |
✓ | ✓ | ✓ | ✓ | ✓ |
verify |
✓ | ✓ | ✓ | ✓ | ✓ |
search |
✓ | ✓ | ✓ | ✓ | ✓ |
hover |
✓ | ✓ | ✓ | ||
right_click |
✓ | ✓ | |||
drag |
✓ | ✓ | |||
navigate |
✓ | ✓ | |||
go_back |
✓ | ||||
swipe |
✓ | ✓ | |||
start_app |
✓ | ✓ | ✓ | ||
stop_app |
✓ | ✓ | ✓ |
Listen for real-time step and screenshot events in interactive mode:
from qirabot import StepEvent, ScreenshotEvent
with bot.task("device-id") as t:
t.on("step", lambda s: print(f"Step {s.number}: {s.action} -> {s.status}"))
t.on("screenshot", lambda s: s.save(f"screenshots/step_{s.number}.png"))
t.click("Login button")Interactive mode keeps the device connection alive across steps. You can run your own Python code between any two device actions — read files, validate data, branch on results, write reports:
import json
import os
from qirabot import Qirabot
bot = Qirabot(os.environ["QIRA_API_KEY"])
# Test data — ready to run, no extra files needed
test_cases = [
{"url": "https://github.com", "expect_keyword": "GitHub"},
{"url": "https://www.wikipedia.org", "expect_keyword": "Wikipedia"},
{"url": "https://news.ycombinator.com", "expect_keyword": "Hacker News"},
]
results = []
with bot.task("device-id", name="heading-check") as t:
for case in test_cases:
# Device: navigate to the page
t.navigate(case["url"])
t.wait_for("Page has finished loading", timeout_ms=15000)
# Device: extract the page heading
heading = t.extract("Get the main heading or site title text")
# Your code: validate between steps
passed = case["expect_keyword"].lower() in heading.lower()
results.append({"url": case["url"], "heading": heading, "passed": passed})
print(f"{'PASS' if passed else 'FAIL'} {case['url']} -> {heading}")
# Conditional: screenshot only on failure
if not passed:
t.screenshot(path=f"{case['expect_keyword']}_fail.png")
print(json.dumps(results, indent=2, ensure_ascii=False))Submit mode returns structured step results for post-processing:
import csv
task_id = bot.submit(
"device-id",
name="product-hunt-scrape",
instruction="Go to producthunt.com, extract the top 5 products with their names and taglines",
)
result = bot.wait(task_id)
with open("steps.csv", "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["step", "action", "status", "duration_ms"])
writer.writeheader()
for step in result.steps:
writer.writerow({
"step": step.number,
"action": step.action,
"status": step.status,
"duration_ms": step.step_duration_ms,
})from qirabot import ActionError, QirabotTimeoutError, DeviceOfflineError
try:
with bot.task("device-id", name="error-demo") as t:
t.navigate("https://httpstat.us/500")
t.verify("Page shows a success message")
except ActionError as e:
print(f"Action failed: {e}")
except DeviceOfflineError:
print("Device is offline")
except QirabotTimeoutError:
print("Operation timed out")MIT