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 submission or phone run 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 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. |
create-account-tool
Link a posting account for the authenticated user on TikTok, Instagram or YouTube.
platform chooses which and defaults to tiktok. The account must already be signed in on
the phone at the given slot; this tool records it, it does not sign anything in.
| Parameter | Type | Description | |
|---|---|---|---|
platform | string | optional | Where the account posts: "tiktok", "instagram" or "youtube". Defaults to "tiktok". |
handle | string | required | Public @handle. Required on every platform. |
slot | string | see below | 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. |
notes | string | optional | Free-form notes about this account. |
google_email | string | see below | The Google account address of this YouTube channel. Required on YouTube, rejected on every other platform. |
The per-phone cap of 4 accounts is a TikTok count, 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.
update-account-tool
Update a posting account the authenticated user is allowed to manage, on any platform. Only the fields you send are changed, and the platform cannot be changed here.
| Parameter | Type | Description | |
|---|---|---|---|
account_id | string (UUID) | required | ID of the account to update, on any platform. |
handle | string | optional | Public @handle. |
slot | string | optional | Phone slot to move the account to, given as the slot UUID from list-phones-tool. A TikTok slot holds at most 4 accounts. |
notes | string | optional | Free-form notes about this account. |
google_email | string | see below | The Google account address of this YouTube channel. Only editable on YouTube; rejected on every other platform. |
Moving slot onto a registered phone needs a control grant on it, the same as
create-account-tool, 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 have a phone assigned 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 have a phone assigned, otherwise the call is refused.
You may post to an account you own, or to one whose phone you hold a submit grant on. Either
way, you must still be able to publish to the account's phone: an account you own on a phone
that was moved to someone else, or whose rental ended, is refused with
This account is on a phone you cannot publish to.
cancel-submission-tool
Cancel a queued or in-flight submission. This stops the phone between steps; a not-yet-started job simply gets marked cancelled.
| 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.
Phones
These reach the phone hardware directly and are platform-agnostic: they drive a handset,
not an app. All of them require a grant on the slot, and the phone must have live video
(video_live: true) 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 status. Takes no parameters.
Returns one object per accessible slot: slot, name, video_live, input_present,
model, os_version. 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 a human label for the same phone
(such as "slot1") 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.
list-phone-runs-tool
List the phone runs visible to the authenticated user for one phone, newest first.
| Parameter | Type | Description | |
|---|---|---|---|
slot | string | required | Phone slot UUID from list-phones-tool. |
page | integer | optional | Page number, defaulting to 1. |
Returns data plus a meta object with current_page, last_page, per_page, total,
20 runs per page. A run is created for each queued macro, device command or agent task; see
phone-command-tool, run-macro-tool and run-phone-agent-tool below.
get-phone-run-tool
Get one visible phone run by its run id.
| Parameter | Type | Description | |
|---|---|---|---|
slot | string | required | Phone slot UUID from list-phones-tool. |
run_id | string | required | Phone run UUID. |
This is the polling tool for queued phone work. Returns id, slot, kind
(macro, command or agent), status (queued, running, succeeded, failed or
cancelled), label, result, error, started_at, finished_at, created_at. A run on
another slot, or one you cannot see, reads as Run not found.
phone-command-tool
Send a device command to a farm phone.
| Parameter | Type | Description | |
|---|---|---|---|
slot | string | required | Phone slot to command, given as the slot UUID from list-phones-tool. Requires video_live: true. |
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 | none | Read the phone clipboard back. |
open_url | url | Open an https URL, a custom scheme, or a deep link such as mobilenotes://. |
reboot | none | Restart the phone. |
clear_photos | none | Empty the camera roll. |
get_ip | none | Report the phone's IP address. |
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 returns a phone run id. Poll it
with get-phone-run-tool: the run stays running until the phone has actually run the
command, then succeeds, or fails if it could not. For clipboard_get and get_ip, the
run's result.value holds the clipboard text or the phone's IP. Every other op finishes
with result: null.
phone-control-tool
Send a raw input primitive to a farm phone. Unlike phone-command-tool, which returns a
run to poll, this runs synchronously and returns once the phone acknowledges it.
| Parameter | Type | Description | |
|---|---|---|---|
slot | string | required | Phone slot to control, given as the slot UUID from list-phones-tool. Requires video_live: true. |
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, back, run_shortcut, enter, backspace, copy, cut, paste, select_all. |
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, such as 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. Requires video_live: true. |
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 screen 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. Requires video_live: true. |
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.
Screen reads are capped at 60 per minute, and that budget is shared with the REST and
WebSocket screen reads. Past it the call answers
Too many screen reads. Try again in N seconds.
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. Requires video_live: true. |
workflow | string | one of | Named workflow 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 get-phone-run-tool, or the phone
(phone-snapshot-tool or phone-ocr-tool), 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. Requires video_live: true. |
task | string | required | Plain-language task for the on-phone GUI agent to carry out. |
The call only acknowledges the start; the agent streams progress separately. Watch the run
(via get-phone-run-tool) or the phone (via phone-snapshot-tool) 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 tools read your subscription and hand back links: a Stripe Checkout page, or an approval page. 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: whether there is one, how many phones it covers, the price per phone, and
the monthly total. Takes no parameters. Every assigned phone runs on its own monthly
cycle, so lines carries one entry per phone with its own status and renews_at. 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, the total phones, and normalized items. Checkout saves the card
and holds one month per phone on it; the held amount is charged when an admin assigns each
phone. Fails if a rental is already active with
You already have an active rental subscription. Use request-phones-tool to change how many phones it covers.
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, held on the card now and charged when each phone is assigned. Every phone runs on its own monthly cycle from its own assignment date, so adding one does not move any existing renewal date and nothing is prorated. Removing phones takes effect at each removed phone's own 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 request_id, to_phones, approval_url, expires_at, resolved_at, and a
status of pending, approved, declined, failed (Stripe refused, nothing charged) or
expired. Requests belonging to anyone else answer Billing request not found.

