Tool reference
All 24 tools, in the order the server registers them. Every call is scoped to the caller's own accounts and phone grants, and anything touching a phone is asynchronous.
Tools marked async queue work and return immediately — poll the account or submission for the outcome.
Accounts and submissions live on a platform of tiktok, instagram or youtube, which
defaults to tiktok. Account ids are UUIDs that identify their own platform, so only
the three tools with no id to read it from — list-accounts-tool, create-account-tool,
create-submission-tool — take a platform parameter. See
Platforms for what actually differs between them.
Accounts
list-accounts-tool
List the authenticated user's posting accounts on TikTok, Instagram and YouTube, newest
first, 50 per page. Every platform is included unless platform narrows it to one.
| Parameter | Type | Description | |
|---|---|---|---|
page | integer | optional | Page number to fetch (defaults to 1, min 1). Each page holds up to 50 accounts. |
platform | string | optional | Only accounts on this platform: "tiktok", "instagram" or "youtube". Omit for every platform, merged newest-first. |
Returns data plus a meta object with current_page, last_page, per_page, total.
Each account carries its own platform.
get-account-tool
Fetch a single posting account the authenticated user is allowed to view, on any platform.
| Parameter | Type | Description | |
|---|---|---|---|
account_id | string (UUID) | required | ID of the account to fetch, on any platform. |
This is the polling tool for login lifecycle. Read status against the
account status table.
create-account-tool — async
Create a posting account for the authenticated user. platform chooses where it posts and
defaults to tiktok.
TikTok needs username, password and slot, and its login is queued on the
assigned phone slot. The account comes back immediately in pending_login; poll
get-account-tool for the outcome.
Instagram and YouTube need only a handle, and there are three outcomes:
- with
usernameandpassword— createdpending_loginand its login is queued on the phone, exactly as TikTok's.slotmust name a phone you control, or there is nothing for the login to run on. - with
already_signed_in: true— createdactive, nothing queued. Any credentials given are still stored, so a laterlogin-account-toolcan drive the phone. - with no credentials — created
activeas a record only: a handle somebody signed in on the handset by hand, which the farm holds no way to sign in itself.
On any platform, already_signed_in: true means the account comes back active and ready
to post straight away, nothing is queued, and the phone is never contacted — so this works
even while the handset is offline. There is nothing to poll for.
| Parameter | Type | Description | |
|---|---|---|---|
platform | string | optional | Where the account posts: "tiktok", "instagram" or "youtube". Defaults to "tiktok". |
username | string | see above | Login username or email. Required on TikTok. Optional on Instagram and YouTube, where supplying it with a password is what queues a login. |
password | string | see above | Login password. Required on TikTok, even with already_signed_in, so the account can be signed back in if its session drops. Optional on Instagram and YouTube. |
slot | string | see above | Phone slot the account is bound to, given as the slot UUID from list-phones-tool. Required on TikTok, where a slot holds at most 4 accounts. Optional on Instagram and YouTube, but a login has no phone to run on without it. |
handle | string | see above | Public @handle. Required on Instagram and YouTube — nothing discovers it for us. Optional on TikTok. |
already_signed_in | boolean | optional | Someone already signed this account in on the phone by hand. Creates it ready to post and skips the login. Defaults to false. |
notes | string | optional | Free-form notes about this account. |
The per-phone cap of 4 accounts is a TikTok count and is enforced at validation on
TikTok creates only. If a phone is registered for the slot, you must hold control
permission on it — on any platform — or the call fails with
You do not have permission to control the phone for this slot.
delete-account-tool
Delete a posting account the authenticated user is allowed to manage, on any platform. This removes the account from the farm; it does not delete the account on the platform itself.
| Parameter | Type | Description | |
|---|---|---|---|
account_id | string (UUID) | required | ID of the account to delete, on any platform. |
Returns { "deleted": true, "id": <id> }.
Submissions and uploads
A submission is one video published to one account. The account must be active — logged
in on its phone — or the call is rejected.
list-submissions-tool
List the calling user's submissions, most recent first, 50 per page. Every platform is
included unless platform narrows it to one; each submission carries its own platform.
| Parameter | Type | Description | |
|---|---|---|---|
page | integer | optional | Page number (1-based). Defaults to 1. |
platform | string | optional | Only submissions posted to tiktok, instagram or youtube. Omit for every platform. |
Returns data, current_page, last_page, total.
get-submission-tool
Fetch a single submission the calling user is allowed to view.
| Parameter | Type | Description | |
|---|---|---|---|
submission_id | integer | required | ID of the submission to fetch. |
This is the polling tool for publishing. Read status against the
submission status table.
create-upload-url-tool
Mint a short-lived signed URL for uploading a video directly, when you do not have a public
video_url. Takes no parameters.
Returns upload_id, upload_url, and expires_at. POST or PUT the raw video (or a
multipart video field) to upload_url within 15 minutes, then pass upload_id to
create-submission-tool.
The signature is the grant — the upload endpoint takes no bearer token, and the URL
embeds the owning user so create-submission-tool can verify it belongs to you.
create-submission-tool — async
Queue a video to be published to a TikTok, Instagram or YouTube account. Returns the
submission immediately, in pending; the phone is then driven through that platform's app.
Poll get-submission-tool for the outcome.
| Parameter | Type | Description | |
|---|---|---|---|
platform | string | optional | Platform to post to: "tiktok" (default), "instagram" or "youtube". |
account_id | string (UUID) | required | ID of the account to post as. Required unless the deprecated tik_tok_account_id is given. |
tik_tok_account_id | string (UUID) | deprecated | Alias for account_id. Still accepted; prefer account_id. |
video_url | string | one of | Public https URL of the video, downloaded by the server. Mutually exclusive with upload_id. |
upload_id | string | one of | Upload id from create-upload-url-tool. Mutually exclusive with video_url. |
caption | string | see below | Text for the post. Up to 2200 characters on TikTok and Instagram. On YouTube this is the Short's title — required, and capped at 100 characters. |
draft | boolean | optional | Save as a draft on the phone instead of publishing. Supported on all three platforms. |
Provide the video via exactly one of video_url or upload_id — supplying both is a
validation error, and supplying neither is too.
account_id is looked up on the requested platform's table only. An id belonging to
another platform answers Account not found., the same as a made-up one — so a platform
that does not match the account reads as a missing account, not as a mismatch.
Omitting caption on YouTube is refused with
Give the Short a title. YouTube names an untitled Short after the date. — an untitled
Short really does end up named after the day it was posted.
The account must be active, otherwise:
This account is not logged in on its phone yet, so it cannot post.
You may post to an account you own, or to one whose phone you hold a submit grant on.
cancel-submission-tool
Cancel a queued or in-flight submission. The running pipeline stops driving the phone between steps; a not-yet-started job no-ops.
| Parameter | Type | Description | |
|---|---|---|---|
submission_id | integer | required | ID of the submission to cancel. |
Only works while the submission is non-terminal. Already finished returns
This submission has already finished.
delete-submission-tool
Delete a finished submission (published, drafted, failed, or cancelled) and its
stored video.
| Parameter | Type | Description | |
|---|---|---|---|
submission_id | integer | required | ID of the submission to delete. |
In-flight submissions cannot be deleted — cancel first. Otherwise:
Submissions can only be deleted once they have finished processing.
Account lifecycle
login-account-tool — async
Log an account in on its phone, on any platform. Queues the login and returns immediately;
poll get-account-tool to observe the outcome.
| Parameter | Type | Description | |
|---|---|---|---|
account_id | string (UUID) | required | The ID of the account to log in, on any platform. |
Sets the account to pending_login and clears any previous login_error. If a login is
already mid-flight it declines with This account is already logging in. rather than
starting a second one.
Requires stored credentials. Instagram and YouTube accounts may be record-only —
signed in on the handset by hand, with no username/password held here — and those are
active and post fine, but there is nothing to type into a login form for them. The call
is refused with a message saying so; give the account a username and password first.
logout-account-tool — async
Log an account out on its phone. Works on TikTok, Instagram and YouTube. Queues the logout
and returns immediately; poll get-account-tool to observe the outcome.
| Parameter | Type | Description | |
|---|---|---|---|
account_id | string (UUID) | required | The ID of the account to log out, on any platform. |
A platform's flow can decline after it checks the phone; when it does, the account status
is left unchanged rather than reported as signed out. Already logged out returns
This account is already logged out.
stop-login-tool
Cancel a login in progress on any platform: stop the on-phone agent and move the account off "logging in".
| Parameter | Type | Description | |
|---|---|---|---|
account_id | string (UUID) | required | The ID of the account whose login should be stopped, on any platform. |
Stopping the on-phone agent is best-effort — if nothing is running or the phone is
unreachable, the account is still moved out of the stuck state. It lands in failed with
login_error set to Login stopped. This is the tool for an account wedged in
logging_in.
Phones
These reach the farm hardware directly, and are platform-agnostic — they drive a handset, not an app. All of them require a grant on the slot, and the slot must be cast/live (video streaming from the phone) for commands, raw input, snapshots, OCR, macros, and the agent.
list-phones-tool
List the farm phone slots the authenticated user can view, with their live video and session state. Takes no parameters.
Returns one object per accessible slot: slot, name, video_live, input_present,
how, ip, model, os_version, session_name. Slots you hold no grant on are omitted.
Start here to find a slot id and confirm it is live before commanding it.
slot is the UUID every other phone tool wants. name is the human label for the same
phone ("slot1", "slot12") and is never accepted in its place. Pass a name where a slot
is wanted and it matches no phone, which surfaces as an authorization error rather than a
missing one.
phone-command-tool
Queue a device command on a farm phone, then trigger its on-phone Shortcut to run it.
| Parameter | Type | Description | |
|---|---|---|---|
slot | string | required | Phone slot to command, given as the slot UUID from list-phones-tool. Must be cast/live. |
op | string | required | The command op. See below. |
text | string | optional | Text to set on the clipboard (for clipboard_set). |
url | string | optional | URL or deep link to open (for open_url). |
level | number | optional | Brightness level 0–1 (for brightness). |
on | boolean | optional | Desired on/off state (for wifi, airplane, cellular, flashlight). |
Supported ops:
| Op | Field | Effect |
|---|---|---|
clipboard_set | text | Set the phone clipboard. |
clipboard_get | — | Read the phone clipboard back. |
open_url | url | Open a URL / deep link. |
reboot | — | Restart the phone. |
clear_photos | — | Empty the camera roll. |
get_ip | — | Report the phone's LAN IP. |
brightness | level | Set screen brightness (0–1). |
wifi | on | Toggle Wi-Fi. |
airplane | on | Toggle airplane mode. |
cellular | on | Toggle cellular. |
flashlight | on | Toggle the flashlight. |
Op-specific fields are only sent when provided. The tool confirms the command was queued
and the Shortcut triggered — it does not return the command's result inline. For
clipboard_get and get_ip, read the value back from the farm.
phone-control-tool
Send a raw input primitive to a farm phone. Unlike phone-command-tool, which queues an
on-phone Shortcut, these are direct farmd socket ops and run synchronously — the call
returns once farmd acks.
| Parameter | Type | Description | |
|---|---|---|---|
slot | string | required | Phone slot to control, given as the slot UUID from list-phones-tool. Must be cast/live. |
op | string | required | Input op: tap, swipe, hotkey, type. |
fx, fy | number | for tap | Tap point, fractions 0–1. |
fx1, fy1, fx2, fy2 | number | for swipe | Swipe start and end, fractions 0–1. |
steps | integer | optional | Swipe step count controlling gesture speed (1–500). Defaults to 20. |
key | string | for hotkey | One of home, app_switcher, control_center, notifications, paste, run_shortcut. |
text | string | for type | ASCII text to type into the focused field. |
Coordinates are fractions of the screen: 0 is left/top, 1 is right/bottom. Missing
op-specific fields are rejected with a message naming what the op needs — tap requires fx and fy (0..1).
phone-snapshot-tool
Capture a JPEG snapshot of a farm phone screen.
| Parameter | Type | Description | |
|---|---|---|---|
slot | string | required | Phone slot to snapshot, given as the slot UUID from list-phones-tool. Must be cast/live. |
width | integer | optional | Snapshot width in pixels (120–2000). Defaults to the configured farm width. |
Returns an image, not JSON. This is the way to see what a phone is actually showing — the fastest way to understand a stuck login or a failed publish.
phone-ocr-tool
Read the on-screen text of a farm phone: snapshots the slot and returns the OCR-recognized text. Read-only and synchronous.
| Parameter | Type | Description | |
|---|---|---|---|
slot | string | required | Phone slot to read, given as the slot UUID from list-phones-tool. Must be cast/live. |
width | integer | optional | Snapshot width in pixels (120–2000). Defaults to the configured farm width. |
The text comes back wrapped in a delimited envelope marking it as untrusted device-screen data, not instructions — whatever is on the phone is reported, never acted on. Use it when you want to match on text rather than look at a picture.
run-macro-tool — async
Run a step list on a farm phone: either a named workflow from the registry, or a raw step list.
| Parameter | Type | Description | |
|---|---|---|---|
slot | string | required | Phone slot to run on, given as the slot UUID from list-phones-tool. Must be cast/live. |
workflow | string | one of | Named workflow / system macro to resolve and run. |
params | object | optional | Scalar params for the workflow's {{placeholders}}. Non-scalar values are dropped. |
steps | array | one of | Raw step list run as-is; each step needs an action. |
Give exactly one of workflow or steps, or the call fails with
Provide exactly one of "workflow" or "steps". A full workflow can run for minutes, so
this queues the run and returns immediately — poll the phone (phone-snapshot-tool or
phone-ocr-tool) or the affected account for the outcome.
run-phone-agent-tool — async
Ask the on-phone GUI agent to carry out a narrow task on a farm phone.
| Parameter | Type | Description | |
|---|---|---|---|
slot | string | required | Phone slot to run the agent on, given as the slot UUID from list-phones-tool. Must be cast/live. |
task | string | required | Plain-language task for the on-phone GUI agent to carry out. |
The call only acks the start; the agent streams progress separately. Watch the phone (via
phone-snapshot-tool) or the account for the outcome. If the farm's GUI agent is not
enabled or not ready, the call fails with
The farm GUI agent is not enabled or not ready.
Keep task narrow. It is a GUI agent driving a real phone, not a general assistant.
Billing
Renting phones costs money, and by default no tool here can spend yours. These four read your subscription and hand back links — a Stripe Checkout page, or an approval page on your dashboard. Both are inert until you act on them.
The one exception is a standing allowance you set yourself at
0bull.net/settings/mcp, capped at a phone count you
choose. Within it, request-phones-tool applies an increase on the spot and places the
order. Nothing is charged until an admin assigns the phone. It is off unless you turn it on.
get-billing-tool
Read the rental subscription: whether there is one, how many phones it covers, the price
per phone, its Stripe status, and when it renews. Takes no parameters. The response
always includes pending_orders, a list of orders waiting for an admin to assign phones;
those orders have not been charged. Start here before proposing any change.
start-rental-tool
Open a Stripe Checkout page for a first subscription.
| Parameter | Type | Description | |
|---|---|---|---|
phones | integer | one of | Number of phones for one country, 1 to 50. Give this or items. |
country | string | optional | ISO 3166-1 alpha-2 country for phones, such as ES, US or JP. Defaults to ES. |
items | array | one of | Rows of { country, quantity } for a multi-country order. Use 1 to 10 rows and at most 50 phones total. Each country may appear once. |
accept_terms | boolean | required | Must be true. Before calling, show terms and privacy, and get agreement to them and to monthly renewal charges for each assigned phone. |
Returns checkout_url and normalized items. Nothing is charged at checkout and no phones
are assigned until an admin assigns concrete phones. Fails if a subscription is already
active. Use request-phones-tool to change an existing one.
request-phones-tool
Change how many phones the subscription covers.
| Parameter | Type | Description | |
|---|---|---|---|
phones | integer | one of | The total the subscription should cover afterwards, 1 to 50. Give this or add, not both. |
add | integer | one of | Signed change to the current count, from -49 to 49. For example, add: 1 adds one and add: -2 removes two. Give this or phones, not both. |
accept_terms | boolean | required | Must be true. Before calling, show terms and privacy, and get agreement to them and to monthly renewal charges for each assigned phone. |
Read applied in the response to know what happened. It says whether the order was placed
without approval, not whether money moved:
applied | What happened | What you get |
|---|---|---|
false | Filed, waiting on you | request_id, approval_url, and an estimated charge. Nothing has been charged. |
true | Order placed under your standing allowance | Phones are pending admin assignment. The card is charged only when each phone is assigned. |
Adding phones costs full list price per phone when each phone is assigned, and your renewal date does not move: the added phones join the cycle you are already on, so their first month runs to your existing period end. Nothing is prorated, so the day of the month makes no difference to the price. Removing phones takes effect at the next renewal, with no credit now.
A request expires in an hour and can be answered once.
Auto-apply only ever covers an increase that lands at or below your allowance. An increase past it, and every decrease, still asks — the allowance is permission to spend your money, not to take your phones away.
get-billing-request-tool
Check what you decided about a filed change.
| Parameter | Type | Description | |
|---|---|---|---|
request_id | string | required | The request_id from request-phones-tool. |
Returns pending, approved, declined, failed (Stripe refused, nothing charged) or
expired. Requests belonging to anyone else read as not found.

