Home › Developers

MKY Remote API and AI connection

Your software, or your AI assistant, works the iPhones you rent from us: schedule posts, drive a phone from code, or connect Claude or ChatGPT and just ask.

Updated

What you can do

Three ways in. Each one reaches only the phones rented to your account.

Schedule posts

Send a video or photos, a caption, a phone and a time. The phone posts it as an Instagram reel or post, a TikTok or a Facebook reel, and you follow it until it is live.

Drive a phone

Read the screen as a picture or as text with positions, then tap, swipe, type, press home or lock, open and close apps, and open web links.

Connect Claude, ChatGPT or Codex

Add https://mkyremote.com/mcp to your assistant, sign in to MKY Remote, and drive your phones in plain words.

Getting access

API keys come from us. AI assistants that sign in to MKY Remote need no key.

Ask for a key

Message @MakyrRec on Telegram or write to [email protected] with these answers, and we create your key the same day.

  1. Your MKY Remote username: the key works on that account's phones.
  2. Which post types you will use, so we check those on your phones first.
  3. Roughly how many posts a day, on how many phones, and how large the files are.
  4. Should the key work only from your servers? If yes, their IP addresses.

Keep it safe

  • 30 days per key. We renew it before it ends. GET /api/v1/me shows the date.
  • It is a password. Anyone who has it can post and drive your phones. Keep it on your server, never in a web page or an app you give out. If it leaks, tell us and we turn it off at once.
  • Your phones only. A key reaches only the phones rented to your account; other customers' phones do not exist for it. It does not sign in to the website.
  • We stay in control. We can turn a key off, back on, or delete it at any time.
  • Optional IP lock. We can lock the key to your servers' IP addresses, so a copied key works nowhere else.

Authentication and base URL

Every call goes to https://mkyremote.com/api/v1/ over HTTPS, with your key in the Authorization header.

Authorization: Bearer YOUR_KEY

Check your key:

curl https://mkyremote.com/api/v1/me \
  -H "Authorization: Bearer YOUR_KEY" \
  -A "YourCompany/1.0"

The answer:

{"user": "yourname",
 "key": {"id": "k_1a2b3c4d", "prefix": "mky_Ab12", "created": "2026-10-29T09:00:00Z",
         "expires": "2026-11-28T09:00:00Z", "days_left": 30},
 "limits": {"calls_per_minute": 60}}

Then list your phones with GET /api/v1/phones:

{"phones": [{"id": "00008030-…", "name": "iPhone 1", "online": true,
             "automation": true, "rental_expires": "2026-11-27T09:00:00Z",
             "engine": "standard",
             "control": ["screenshot", "screen", "tap", "swipe", "type", "button",
                         "apps/open", "apps/close", "url"]}]}
  • Keys start with mky_. Send one with every call as Authorization: Bearer YOUR_KEY. Answers to a working key also carry an X-API-Key-Expires header with the key's end date.
  • Name your software. Send a User-Agent such as YourCompany/1.0. Our network refuses Python urllib's default one; Python requests, Node, curl and Go are fine as they are.
  • Phones by id or name. Wherever a call takes a phone, send its id or its name from the list (not case-sensitive). Names follow your own numbering ("iPhone 1", or the name you gave the phone) and can shift when phones are added or removed; the id never changes.
  • JSON in and out. Answers are JSON (screenshots are images) and times are in UTC. An error answers with error (a code) and message (what to fix).

Posting API

Schedule a post with one call, then follow, change, cancel or retry it with its id.

CallWhat it does
POST /api/v1/postsSchedules a post (multipart); returns its id
GET /api/v1/postsYour posts, newest first; filter by status, phone or date
GET /api/v1/posts/{id}One post and its status
PATCH /api/v1/posts/{id}New caption or time, while it is still scheduled
DELETE /api/v1/posts/{id}Cancels a scheduled post; removes a finished one from your list
POST /api/v1/posts/{id}/retryRuns a failed post again on the same phone, within 24 hours

Schedule a post

Send the post as multipart/form-data to POST /api/v1/posts. One call schedules one post; with phones, the same post goes to each phone, 8 to 10 minutes apart.

FieldRequiredNotes
typeyesinstagram_reel, instagram_post, tiktok or facebook_reel
phoneone of phone, account, phonesThe id or name from GET /api/v1/phones, e.g. iPhone 1
accountThe name you gave a phone on the scheduler page, with or without @, e.g. @yourbrand
phonesSeveral phones, comma separated, up to 25, e.g. iPhone 1, iPhone 2; each posts 8 to 10 minutes after the previous one
mediayesOne MP4 or MOV video for reels and TikTok; 1 to 10 photos or videos for an Instagram post (repeat media). Up to 100 MB per call
captionnoUp to 2,200 characters, emoji included
post_atyesISO 8601 with your time zone (2026-11-02T18:00:00+01:00), a Unix time, or now; up to 60 days ahead
max_delay_minutesnoPosts at a random moment 8 to this many minutes after post_at: 8 to 60, default 12. 0 posts on time
musicnoInstagram reels: a song name; the phone finds the track and adds it
hide_likesnoInstagram reels: true hides the like count. Not with trial_reel
trial_reelnoInstagram reels: true posts it as a trial reel
warmup_afternoInstagram reels and posts: true runs a 14 to 18 minute Instagram warm-up after the post. Off unless you send it

Example call:

curl -X POST https://mkyremote.com/api/v1/posts \
  -H "Authorization: Bearer YOUR_KEY" \
  -A "YourCompany/1.0" \
  -F type=instagram_reel \
  -F "phone=iPhone 1" \
  -F post_at=2026-11-02T18:00:00+01:00 \
  -F "caption=Monday drop #brand" \
  -F [email protected]

The answer comes right away, with status 201:

{"id": "3f8c2a1e-7b4d-4c9e-a1f2-5d6e7f8a9b0c", "status": "scheduled", "count": 1,
 "posts_between": ["2026-11-02T17:08:00Z", "2026-11-02T17:12:00Z"],
 "posts": [{"id": "3f8c2a1e-7b4d-4c9e-a1f2-5d6e7f8a9b0c", "status": "scheduled",
            "type": "instagram_reel", "phone": {"id": "00008030-…", "name": "iPhone 1"},
            "caption": "Monday drop #brand", "scheduled_for": "2026-11-02T17:09:41Z",
            "started_at": null, "finished_at": null, "result": null,
            "media": [{"name": "video.mp4"}],
            "options": {"music": null, "hide_likes": false, "trial_reel": false,
                        "warmup_after": false},
            "source": "api", "created_at": "2026-10-29T10:15:00Z"}]}

Keep the id: it is how you follow, change or cancel the post. posts_between is when the first phone posts; with phones, each post in posts has its own scheduled_for.

StatusMeaningWhat you can do
scheduledWaiting for its timeChange it (PATCH) or cancel it (DELETE)
postingThe phone is publishing it nowWait; it can no longer be changed or cancelled
postedPublishedRemove it from your list (DELETE)
failedNot published; result says why, e.g. "Share button not found"Retry it within 24 hours (POST /api/v1/posts/{id}/retry), or remove it
cancelledCancelled by you; its media is deletedRemove it from your list, or send it again

Follow, change or cancel

  • Follow it with GET /api/v1/posts/{id}: its status moves from scheduled to posting to posted, or to failed with the reason in result.
  • A failed attempt is usually retried once by itself a minute later; meanwhile the post shows scheduled and result reads Retrying after: ….
  • Posts stay in your list for 24 hours after they finish, failed ones for 7 days.
  • To change a scheduled post, send JSON with caption, post_at or both. A new post_at gets a new random delay, like a new post; add max_delay_minutes to set it (0 posts on time).
  • To list your posts, filter GET /api/v1/posts with status, phone, since, until (by scheduled time) and limit (up to 200, default 50). The list also holds what you scheduled on the website; source says where each one came from (api, web, or vault for the Media library).

Change a scheduled post:

curl -X PATCH https://mkyremote.com/api/v1/posts/3f8c2a1e-7b4d-4c9e-a1f2-5d6e7f8a9b0c \
  -H "Authorization: Bearer YOUR_KEY" -A "YourCompany/1.0" \
  -H "Content-Type: application/json" \
  -d '{"caption": "New text", "post_at": "2026-11-03T18:00:00+01:00"}'

List the failed ones:

curl "https://mkyremote.com/api/v1/posts?status=failed&limit=20" \
  -H "Authorization: Bearer YOUR_KEY" -A "YourCompany/1.0"
# → {"posts": [...], "count": 2, "total": 2}

Phone control

Drive any of your phones yourself: look at the screen, then act on it.

{phone} is the id or the name from GET /api/v1/phones; write a space as %20, e.g. iPhone%201.

Coordinates are fractions of the screen, from 0 to 1, measured from the top-left corner: {"x": 0.5, "y": 0.5} is the middle on every phone. The text that /screen returns comes in the same units, so you can tap what you read.

CallBody (JSON)Notes
GET /api/v1/phones/{phone}Online or not, its engine, what it can do (can) and, on standard phones that are online, its screen size
GET …/screenshotThe screen as an image: PNG on standard phones, JPEG on live-sound phones (see Content-Type)
GET …/screenitems: text, x, y, width, height, center_x, center_y, confidence; top to bottom
POST …/tap{"x": 0.5, "y": 0.62}Add "hold_ms": 1200 to press and hold (up to 5,000)
POST …/swipe{"x1": 0.5, "y1": 0.8, "x2": 0.5, "y2": 0.2}duration_ms 50 to 5,000 (default 300); this one scrolls down
POST …/type{"text": "hello"}Types into the field that has the cursor; up to 2,000 characters
POST …/button{"name": "home"}home or lock. Volume buttons are not available
POST …/apps/open{"app": "instagram"}An app name from the list below, or a bundle id
POST …/apps/close{"app": "instagram"}Standard phones only
POST …/url{"url": "https://…"}Opens a web link (http or https only). Standard phones only

Apps by name

instagram, tiktok, facebook, threads, x (or twitter), reddit, snapchat, youtube, whatsapp, telegram, safari, settings, photos, camera, files and messages.

Standard phones also take any bundle id, such as com.burbn.instagram. Live-sound phones find apps by name, so there you can also send an installed app's name as it shows on the phone.

Example: read the screen, find the text "Follow" and tap its center.

curl https://mkyremote.com/api/v1/phones/iPhone%201/screen \
  -H "Authorization: Bearer YOUR_KEY" -A "YourCompany/1.0"
# → {"items": [{"text": "Follow", "center_x": 0.5, "center_y": 0.515, ...}, ...]}

curl -X POST https://mkyremote.com/api/v1/phones/iPhone%201/tap \
  -H "Authorization: Bearer YOUR_KEY" -A "YourCompany/1.0" \
  -H "Content-Type: application/json" -d '{"x": 0.5, "y": 0.515}'
# → {"ok": true}

Who goes first

  • Every input answers {"ok": true} once the phone has done it.
  • A person comes first. If someone tapped the phone in the last minute (you, or someone on your team in the browser), input answers 409 phone_in_use. Send "force": true in the body to act anyway. Screenshots and /screen are never held back by this.
  • Our own work comes first. While one of your scheduled posts, a warm-up or an Instagram tracking reading runs on the phone, input answers 409 phone_busy, even with force.
  • Your scripts come before your schedule. A scheduled post waits until 2 minutes after your last input on that phone before it starts.
  • Live-sound phones ("engine": "live-sound" in the phone list) do screenshot, screen, tap, swipe, type, button and apps/open. Closing apps and opening links are not available on them yet (501 not_supported). GET /api/v1/phones/{phone} lists exactly what each phone can do.
  • A dark screen: on a live-sound phone, the screenshot and /screen answer 409 screen_off; press home first.
  • An offline phone answers 409 phone_offline; the phone list shows when it is back.

AI connection (MCP)

Connect an AI assistant and drive your phones in plain words: "open Instagram on iPhone 2 and tell me how many followers the account has".

It uses the Model Context Protocol (MCP) at https://mkyremote.com/mcp, with the same phones, limits and rules as the calls above. Claude, ChatGPT and Codex need no key: you add the address, sign in to MKY Remote and click Allow. Other apps can use your API key.

WhereHow to connect
Claude
(claude.ai and the apps)
Settings → Connectors → Add custom connector. Name it MKY Remote, URL https://mkyremote.com/mcp, then Connect. Sign in to MKY Remote and click Allow. In a chat, turn the connector on and ask.
ChatGPTIn settings, turn on developer mode, then create a connector with the URL https://mkyremote.com/mcp and OAuth as the authentication. Sign in to MKY Remote and click Allow.
Codex
(OpenAI)
Run codex mcp add mky --url https://mkyremote.com/mcp, then codex mcp login mky. Sign in to MKY Remote and click Allow.
Claude CodeRun the command below, then /mcp, pick mky and Authenticate. Sign in and click Allow.
Other apps
and agents
A remote MCP server (streamable HTTP) at https://mkyremote.com/mcp. Apps that support MCP sign-in (OAuth) find it on their own; the others send the header Authorization: Bearer YOUR_KEY. OpenAI API: a tool of type mcp with that server_url and header.

Claude Code:

claude mcp add --transport http mky https://mkyremote.com/mcp

The 12 tools the assistant gets:

ToolWhat it does
list_phonesYour phones: name, id, online or not, and what each one can do
look_at_screenA picture of the phone's screen as it is now
read_screenEvery text on the screen, top to bottom, with the position of its center, ready to tap
tapTap at x, y (fractions of the screen); hold_ms to press and hold
tap_textFind a label on the screen, e.g. "Follow", and tap its center
swipeDrag a finger from (x1, y1) to (x2, y2)
scrollScroll down, up, left or right by about half a screen
type_textType into the field that has the cursor
press_buttonHome (also wakes a dark screen) or lock
open_appOpen an app by name or bundle id
close_appClose an app completely (standard phones)
open_linkOpen an http or https link (standard phones)

Things to ask

  • "Which of my phones are online?"
  • "On iPhone 1, open TikTok and describe what is on the screen."
  • "On iPhone 3, open Safari and go to https://instagram.com/yourbrand, then take a screenshot."

You stay in charge

  • It sees only your phones: the ones rented to the account you signed in with, never anyone else's.
  • Two-factor sign-in works here too: if your account has it on, signing in asks for your 6-digit code as well.
  • It is told to do only what you ask, and never to post, follow, like, comment or message on its own. Claude and ChatGPT also ask you before they use a tool, unless you allow it.
  • Turn it off any time: remove the connector in your app, or ask us and we turn that sign-in off at once (signing in again does not turn it back on). A sign-in stays connected while you use it; after 30 days without use, sign in again.
  • Your API key is a password. If an app can neither sign in nor send a header, it can put the key in the address: https://mkyremote.com/mcp/YOUR_KEY. Anyone who has that address can drive your phones, so do not share it.
  • Same rules as the API: a person tapping the phone comes first, our scheduled posts and warm-ups come first, 60 calls a minute, and every call is logged.

For MCP client builders

  • Transport: streamable HTTP, answered as plain JSON (no event stream) and stateless. Send JSON-RPC with POST; batches work; GET answers 405. Protocol versions 2025-06-18, 2025-03-26 and 2024-11-05. Any origin may call it.
  • Sign-in: OAuth 2.1. A call without a key answers 401 with a WWW-Authenticate header that points to https://mkyremote.com/.well-known/oauth-protected-resource; the server metadata is at /.well-known/oauth-authorization-server. Apps register themselves at /oauth/register (redirect addresses: https, localhost or the app's own scheme), then use /oauth/authorize and /oauth/token with PKCE (S256 only). Scope: phones.
  • Tokens: access tokens last an hour. A refresh token works once and keeps the sign-in alive for 30 days from its last use. These tokens work on /mcp only, never on /api/v1.
  • Errors: a tool that cannot do its job answers a normal result with "isError": true and a message to show the person.

A tool call with an API key:

curl https://mkyremote.com/mcp \
  -H "Authorization: Bearer YOUR_KEY" -A "YourCompany/1.0" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
       "params": {"name": "list_phones", "arguments": {}}}'
# → {"jsonrpc": "2.0", "id": 1, "result": {"content": [{"type": "text",
#     "text": "iPhone 1 (id 00008030-…): online, standard engine, can: screenshot, …"}],
#     "isError": false}}

Rules and limits

These keep the accounts safe and the phones reliable.

  • Timing: posts go out 8 to 12 minutes after post_at unless you set max_delay_minutes (up to 60, or 0 for on time); posts to several phones are spread 8 to 10 minutes apart, so they never land at once.
  • Time window: post_at can be up to 5 minutes in the past and up to 60 days ahead.
  • One at a time: a phone runs one post at a time; a later post waits for it.
  • Logged-in accounts: the account must already be logged in on that phone's app. If the app shows a login, verification or captcha screen, the post fails and result says why.
  • Phones that post: GET /api/v1/phones shows "automation": true for phones that can post by themselves. A live-sound phone posts only the types in its post_types (others answer 409 phone_cannot_post), and music, hide_likes, trial_reel and warmup_after are not available on it yet.
  • Media: up to 100 MB per call. It is kept until the post runs, then 24 hours more for a retry, then deleted from our servers.
  • 60 calls a minute per key, and per AI sign-in. Above that the answer is 429 with a Retry-After header.
  • Logged: every call is logged with its time, action and phone, refused ones too, and we are alerted whenever a key is created or changed.

Errors

Every error answers JSON with a stable code and a message that says what to fix.

{"error": "post_at_in_past", "message": "post_at is 12 min in the past. Send a future time, or now."}
HTTPerrorMeaning
400bad_type missing_phone too_many_phones bad_post_at post_at_in_past post_at_too_far bad_max_delay missing_media bad_media too_many_files one_video_only not_a_video caption_too_long reel_only_option bad_options option_not_available invalid_request bad_status bad_since bad_until bad_json unknown_field nothing_to_change bad_content_type bad_coordinates bad_duration bad_text bad_button bad_app unknown_app bad_urlSomething in the request is wrong
401missing_key invalid_key invalid_token key_expired key_disabled key_revokedNo usable key or sign-in
403ip_not_allowed account_disabled key_not_allowed phone_not_yoursThe key works, but not for this
404phone_not_found account_not_found post_not_found not_foundNot one of yours, or no such call
405method_not_allowedWrong method for this call
409ambiguous_phone phone_cannot_post not_scheduled already_posting not_failed phone_offline phone_in_use phone_busy screen_off busyThe phone or post is in the wrong state for this
410media_goneThe media was deleted 24 hours after the post finished; send the post again
411length_requiredThe request has no body
413too_largeThe upload or the JSON body is too big
415bad_content_typeSend posts as multipart/form-data
429rate_limitedOver 60 calls a minute; wait Retry-After seconds
500server_errorSomething went wrong on our side; try again
501not_supportedThis phone cannot do that yet (see what it can do)
502phone_error app_not_openedThe phone did not manage it; try again
503phone_unreachable starting_up not_availableThe phone or the farm is not ready; try again in a minute

On the AI connection

A refused key gets the same HTTP error and JSON as above. A tool that cannot do its job answers "isError": true with the same message. A message that is not valid JSON-RPC gets a JSON-RPC error: -32700 (not JSON-RPC), -32600 (invalid request), -32601 (unknown method) or -32602 (unknown tool or bad arguments).