Invi Labs Python SDK
Automate real iPhones through the Invi Labs API using your API key and phone ID. Non-jailbroken iPhones require Invi Dongle.
Install
python -m pip install invilabs
Requires Python 3.10 or newer. No third-party runtime dependencies.
Connect and run an action
Set INVI_API_KEY and INVI_PHONE_ID in your environment. Keep your API key private.
import os
from invilabs import Client
client = Client(os.environ["INVI_API_KEY"])
phone = client.phone(os.environ["INVI_PHONE_ID"])
print(phone.details())
phone.tap(100, 200)
phone.save_screenshot("screen.png")
Coordinates use full-resolution screenshot pixels. Calls block until completion;
the SDK waits and retries when the phone reports device_busy. A definite
device_offline response waits for reconnect with delays of 1, 2, and 3 seconds,
up to three retries (six seconds total by default). Each offline
rejection uses a new operation key because the phone received no input.
Uncertain execution is never automatically repeated; use the operation key
to look up its status. Set Client(..., offline_retries=0) to disable reconnect
waiting, or choose an integer from 0 to 30 for longer outages.
AI generation
result = client.generate(
"Generate an email address using example.com",
result_type="email",
)
print(result["data"]["text"])
print(client.balance())
Generation supports text, usernames and email address strings, optionally using a local PNG/JPEG image. It does not create mailboxes. OCR, AI prompts and generation use your shared Account AI balance. No AI-provider key is required.
Features
- Tap, swipe, scroll, type, explicit key presses and opening installed apps.
- Screenshots as PNG bytes or saved to your computer.
- Grouped OCR with text and bounding boxes.
- Single-action AI prompts and structured screen answers.
- Image/video import through Invi Helper on supported dongle phones.
- Device details, balance and operation recovery.
All network operations use the public HTTPS API with your API key. The package contains client code and response contracts; phone control and AI processing run on Invi Labs services. API keys remain on your computer and are sent only as an authentication header to the configured API. HTTP redirects are not followed.
This is an initial release. Device support and feature verification limits are listed in the release notes.
Documentation · Quickstart · Generation and outputs · Errors and recovery
Typing completion
Version 0.1.1 waits for the dongle's final typing acknowledgment. Long typing and
AI actions remain synchronous to Python while the SDK waits for the same server
operation to finish. It never starts a second copy while waiting. Call
phone.press("enter") after phone.type(text) returns to submit a search.
A lost connection can still produce ExecutionUncertainError; inspect the
recorded operation and phone state before deciding on another action.
Tap text with OCR (0.1.2)
result = phone.tap_text("Add a comment")
result = phone.tap_text(r"^(?:add|write) (?:a )?comment[.…]*$", regex=True)
print(result["point"], result["ocr"]["credits_charged"])
Matches the full trimmed OCR block case-insensitively by default; regex uses
re.search. The tap lands randomly inside an inset of the block's bounds.
Use randomize=False for its center. Multiple matches raise
AmbiguousTextError unless a zero-based occurrence is supplied; no match raises
TargetNotFoundError. Neither error taps. Regex substring matches use the whole
block's box. Keep the screen stable between OCR and tap: these are two operations.
Returns match (OCR block), point (x, y pixels), ocr (blocks, credit charge,
balance), and tap (execution status and coordinates). Only the normal OCR credit
charge applies, including when no match is found. A tap failure retains the OCR
result in error.details["ocr"]. This helper makes no VLM request.
Check text presence with OCR (0.1.2)
check = phone.text_exists("Continue", region="middle")
if check["exists"]:
print(check["matches"])
check = phone.text_exists(r"^(?:add|write) (?:a )?comment[.…]*$", regex=True, region="bottom")
check = phone.text_exists("Comments", region=(0, 900, 1170, 600))
Omit region for the full screen. Named regions are full-width horizontal thirds;
custom tuples are (x,y,width,height) in original screenshot pixels. A block's
center determines region membership. Literal and regex rules match tap_text.
Returns exists (bool), matches (OCR blocks), region (resolved pixel box or
None), and ocr (full result with charge and balance). Check result["exists"],
not the dictionary's truthiness. No match is False; OCR failures raise errors.
One billed full-screen OCR request is made regardless of region or match count.
Named regions also read dimensions before and after OCR and reject geometry
changes. This method never taps or calls a VLM.
Jailbroken-only extensions (0.1.5)
The phone.jailbroken namespace is available only for jailbroken iPhones with
our current daemon, UI tweak and container service. Dongle phones reject these
operations; their existing controls and OCR helpers are unchanged.
jb = phone.jailbroken
print(jb.apps())
print(jb.containers.list("com.example.app"))
created = jb.containers.create("com.example.app", "testing")
jb.open_app("com.example.app", cid=created["cid"])
print(jb.containers.active("com.example.app"))
jb.containers.set_active("com.example.app", "default")
jb.containers.delete("com.example.app", created["cid"])
print(jb.ui_dump())
jb.tap_text("Settings")
Containers use the account's existing slot pool and appear in the panel. Container
names contain 1–64 letters, digits, underscores or hyphens. The default container
cannot be deleted. Switching stops the app; open_app(..., cid=...) switches and
launches under one lease. A lost acknowledgment is never automatically replayed.
jb.tap_text uses exact trimmed native UI text and converts point bounds to pixel
coordinates. Matching is case insensitive by default. Duplicate matches require
occurrence=0 (or another zero-based occurrence, in screen reading order).
Disabled and offscreen elements are excluded. UI dump targeting is unbilled and
has no OCR fallback. Native dumps require the UI tweak in the foreground app;
some custom-rendered content is unavailable. phone.tap_text retains its existing
OCR behavior on all phones.
Use the common phone.upload_media(path) and phone.prompt(...) methods on both
phone types. Jailbroken uploads use an exact native Photos transaction and wait
for its asset acknowledgment before cleaning Library storage. Image/video limits,
permissions, shared credits for AI prompts and operation recovery remain the same.
Cloud phone proxy and VPN
SDK 0.1.7 adds connectivity controls for phones with an Invi Labs managed
router. Your API key needs device.edit_proxy and access to the phone.
phone = client.phone(phone_id)
print(phone.connectivity(timeout=30))
locations = phone.vpn_locations(timeout=30)
print(locations)
phone.set_proxy("proxy.example", 1080, "username", "password", timeout=60)
phone.use_vpn(timeout=60) # Restore the currently configured VPN
if locations:
phone.use_vpn(locations[0], timeout=60)
# Equivalent explicit method:
# phone.set_vpn_location(locations[0], timeout=60)
Server-enforced cooldown
A phone accepts one network-change attempt every 60 seconds. The server shares this cooldown across API keys, server instances, proxy changes, VPN changes, and managed-proxy changes. Switching keys or endpoints cannot bypass it. A failed router attempt also consumes the interval. Status and VPN-location reads remain available during the cooldown.
Requests during the cooldown return HTTP 429, error="rate_limited", a
Retry-After header, and details.retry_after_seconds. The SDK raises
InviError with these details and does not retry automatically:
from invilabs import InviError
try:
phone.use_vpn("lax")
except InviError as error:
if error.code == "rate_limited":
print("Wait", error.details["retry_after_seconds"], "seconds")
else:
raise
The optional client timeout argument only bounds how long the request waits;
it cannot shorten or disable the server cooldown.
Each call blocks and accepts a positive timeout in seconds (default 60).
A timed-out change raises ExecutionUncertainError: the server may still
finish it. Read phone.connectivity() before deciding what to do next.
Network changes are never automatically retried, including busy responses.
A timeout is a client waiting limit, not a scheduled switch-back or rollback.
connectivity() reports mode, healthy, available_vpn_locations, and
when available phone_exit_ip, proxy_host, and proxy_port. Locations
come from this phone's assigned router; never assume a fixed global list.
Status probes the router's egress and does not prove every app's traffic path.
Proxy support is SOCKS5. Changes affect the entire phone and may interrupt
its connections. Selecting a VPN location removes the active proxy. Switching
to your own proxy or VPN also stops an active managed-proxy subscription's
renewal. SIM-rented phones keep their managed SIM connection; they report
mode="sim" with no VPN locations and reject network changes. Customer-owned
phones without a managed router cannot use these controls. Viewer sessions
can remain open, though a network switch can temporarily interrupt them.
No separate owner connectivity helper, router URL, or phone MAC is needed.
AI credit pricing
AI usage is metered to 0.000001 credit. Your plan price and monthly credit allowance determine the dollar value of a credit; all plans use the same usage rates.
| Processing | Credits charged |
|---|---|
| 1,000 input tokens | Approximately 0.011111 |
| 1,000 output tokens | Approximately 0.155556 |
| One fresh OCR request | 0.05 |
| Direct taps, swipes, typing, screenshots and media transfers | 0 AI credits |
The exact token formula is (input_tokens + 14 * output_tokens) / 90000, rounded up once per metered invocation to the nearest millionth of a credit. Input includes the system instructions and any image tokens reported by inference. These rates apply to AI prompts, generation and agent model calls on both jailbroken and non-jailbroken phones. Agent flows add the work performed across their model calls and OCR scans.
For example, 2,000 input tokens and 100 output tokens cost 0.037778 credits. On the $45 / 1,000-credit plan, that is approximately $0.00170. A fresh OCR scan costs $0.00225 on that plan. OCR helpers such as tap_text() and text_exists() follow the same OCR metering; each fresh scan is charged, while eligible cache hits and operation replays do not charge again.
Metadata
Release files for invilabs 0.1.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| invilabs-0.1.8.tar.gz | 29.3 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| invilabs-0.1.8-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 57.6 kB
Release files / invilabs-0.1.8.tar.gz
| Download URL | invilabs-0.1.8.tar.gz |
|---|---|
| Size | 29.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
975ceb65bf065603229540a6cd9d9b714337372215ea70d316f80bc6c5ec7d58
|
|
BLAKE2b-256 checksum How to use checksums |
ee6dbc0d9f5b228c1dadf8913fd24e3f6523b179457a54e219d3f11eed1c0830
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency logRelease files / invilabs-0.1.8-py3-none-any.whl
| Download URL | invilabs-0.1.8-py3-none-any.whl |
|---|---|
| Size | 28.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
99df9bc3cd6075c02e952643f4f4b1106ab1d1a00188d56202b32cfcebacbf12
|
|
BLAKE2b-256 checksum How to use checksums |
510c91302c97d387f1a2f27444c26e31e059e58379a8e3c325547a24a51131e6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency logSecurity for invilabs 0.1.8
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for invilabs-0.1.8.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.