{
  "openapi": "3.1.0",
  "info": {
    "title": "MKY Remote API",
    "version": "1.3",
    "summary": "Schedule posts on your rented iPhones and drive them: screen, tap, swipe, type, buttons, apps and links.",
    "description": "Every call carries your API key as `Authorization: Bearer YOUR_KEY` and reaches only the phones rented to your account. Keys start with `mky_`, last 30 days and come from MKY Remote (Telegram @MakyrRec or mky@mkyremote.com). 60 calls a minute per key. Sign-in tokens from the AI connection do not work here.\n\nCoordinates are fractions of the screen from 0 to 1, measured from the top-left corner. Times in answers are UTC. Every error answers JSON `{\"error\": code, \"message\": what to fix}`.\n\nAI assistants use the MCP server at https://mkyremote.com/mcp (same phones, limits and rules); see https://mkyremote.com/developers.html.",
    "termsOfService": "https://mkyremote.com/terms.html",
    "contact": {
      "name": "MKY Remote",
      "url": "https://mkyremote.com/developers.html",
      "email": "mky@mkyremote.com"
    }
  },
  "externalDocs": {
    "description": "MKY Remote API and AI connection",
    "url": "https://mkyremote.com/developers.html"
  },
  "servers": [
    {
      "url": "https://mkyremote.com"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "Your key."
    },
    {
      "name": "Phones",
      "description": "The phones rented to your account."
    },
    {
      "name": "Posts",
      "description": "Schedule, follow, change, cancel and retry posts."
    },
    {
      "name": "Phone control",
      "description": "Look at a phone and act on it. Input waits for a person who is using the phone and never interrupts the farm's own work."
    }
  ],
  "paths": {
    "/api/v1/me": {
      "get": {
        "tags": [
          "Account"
        ],
        "operationId": "getMe",
        "summary": "Check your key",
        "description": "Who the key belongs to, when it expires and the rate limit.",
        "responses": {
          "200": {
            "description": "Your key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Me"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/phones": {
      "get": {
        "tags": [
          "Phones"
        ],
        "operationId": "listPhones",
        "summary": "List your phones",
        "description": "Each phone's id and name, whether it is online, whether it can post by itself, when its rental ends, its engine and what it can be controlled with.",
        "responses": {
          "200": {
            "description": "Your phones.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PhoneList"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/phones/{phone}": {
      "get": {
        "tags": [
          "Phones"
        ],
        "operationId": "getPhone",
        "summary": "Phone status",
        "description": "Online or not, its engine, what it can do and, on standard phones that are online, its screen size.",
        "parameters": [
          {
            "$ref": "#/components/parameters/phone"
          }
        ],
        "responses": {
          "200": {
            "description": "The phone.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PhoneStatus"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "phone_not_found: you have no phone with that id or name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "ambiguous_phone: that name matches several of your phones; send the id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "description": "not_available: phone control is not available right now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/phones/{phone}/screenshot": {
      "get": {
        "tags": [
          "Phone control"
        ],
        "operationId": "getScreenshot",
        "summary": "See the screen",
        "description": "The screen as an image: PNG on standard phones, JPEG on live-sound phones (see Content-Type).",
        "parameters": [
          {
            "$ref": "#/components/parameters/phone"
          }
        ],
        "responses": {
          "200": {
            "description": "The screen.",
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            },
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "image/jpeg": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "phone_not_found: you have no phone with that id or name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "ambiguous_phone, phone_offline, or screen_off (a dark live-sound screen: press home first).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "502": {
            "description": "phone_error: the phone could not do it; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "phone_unreachable or not_available: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/phones/{phone}/screen": {
      "get": {
        "tags": [
          "Phone control"
        ],
        "operationId": "readScreen",
        "summary": "Read the screen",
        "description": "Every text on the screen, top to bottom, with where it is in the same 0..1 units the taps use: tap center_x, center_y to tap it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/phone"
          }
        ],
        "responses": {
          "200": {
            "description": "The text on screen.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ScreenText"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "phone_not_found: you have no phone with that id or name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "ambiguous_phone, phone_offline, or screen_off (a dark live-sound screen: press home first).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "502": {
            "description": "phone_error: the phone could not do it; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "phone_unreachable or not_available: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/phones/{phone}/tap": {
      "post": {
        "tags": [
          "Phone control"
        ],
        "operationId": "tap",
        "summary": "Tap",
        "description": "Tap at x, y. Add hold_ms to press and hold.",
        "parameters": [
          {
            "$ref": "#/components/parameters/phone"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "x",
                  "y"
                ],
                "properties": {
                  "x": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1,
                    "description": "From the left edge: 0 to 1."
                  },
                  "y": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1,
                    "description": "From the top edge: 0 to 1."
                  },
                  "hold_ms": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 5000,
                    "default": 0,
                    "description": "Press and hold for this many milliseconds, e.g. 1200."
                  },
                  "force": {
                    "type": "boolean",
                    "default": false,
                    "description": "Act even though a person tapped this phone in the last minute."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done: the phone has carried it out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "400": {
            "description": "bad_json, bad_coordinates or bad_duration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "phone_not_found: you have no phone with that id or name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "ambiguous_phone, phone_offline, phone_busy (the farm is using the phone; force does not help) or phone_in_use (a person tapped it in the last minute; send \"force\": true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "too_large: the JSON body is over 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "501": {
            "description": "not_supported: this phone cannot do that yet; GET /api/v1/phones/{phone} lists what it can do.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "phone_error: the phone did not manage it; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "phone_unreachable or not_available: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/phones/{phone}/swipe": {
      "post": {
        "tags": [
          "Phone control"
        ],
        "operationId": "swipe",
        "summary": "Swipe",
        "description": "Drag a finger from (x1, y1) to (x2, y2). From y1 0.8 to y2 0.2 scrolls down.",
        "parameters": [
          {
            "$ref": "#/components/parameters/phone"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "x1",
                  "y1",
                  "x2",
                  "y2"
                ],
                "properties": {
                  "x1": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1,
                    "description": "Start, from the left edge."
                  },
                  "y1": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1,
                    "description": "Start, from the top edge."
                  },
                  "x2": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1,
                    "description": "End, from the left edge."
                  },
                  "y2": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1,
                    "description": "End, from the top edge."
                  },
                  "duration_ms": {
                    "type": "integer",
                    "minimum": 50,
                    "maximum": 5000,
                    "default": 300
                  },
                  "force": {
                    "type": "boolean",
                    "default": false,
                    "description": "Act even though a person tapped this phone in the last minute."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done: the phone has carried it out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "400": {
            "description": "bad_json, bad_coordinates or bad_duration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "phone_not_found: you have no phone with that id or name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "ambiguous_phone, phone_offline, phone_busy (the farm is using the phone; force does not help) or phone_in_use (a person tapped it in the last minute; send \"force\": true). busy: another swipe was still running.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "too_large: the JSON body is over 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "501": {
            "description": "not_supported: this phone cannot do that yet; GET /api/v1/phones/{phone} lists what it can do.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "phone_error: the phone did not manage it; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "phone_unreachable or not_available: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/phones/{phone}/type": {
      "post": {
        "tags": [
          "Phone control"
        ],
        "operationId": "typeText",
        "summary": "Type text",
        "description": "Types into the field that has the cursor (tap the field first).",
        "parameters": [
          {
            "$ref": "#/components/parameters/phone"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "text"
                ],
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 2000
                  },
                  "force": {
                    "type": "boolean",
                    "default": false,
                    "description": "Act even though a person tapped this phone in the last minute."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done: the phone has carried it out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "400": {
            "description": "bad_json, bad_text.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "phone_not_found: you have no phone with that id or name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "ambiguous_phone, phone_offline, phone_busy (the farm is using the phone; force does not help) or phone_in_use (a person tapped it in the last minute; send \"force\": true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "too_large: the JSON body is over 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "501": {
            "description": "not_supported: this phone cannot do that yet; GET /api/v1/phones/{phone} lists what it can do.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "phone_error: the phone did not manage it; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "phone_unreachable or not_available: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/phones/{phone}/button": {
      "post": {
        "tags": [
          "Phone control"
        ],
        "operationId": "pressButton",
        "summary": "Press a button",
        "description": "Home (also wakes a dark screen) or lock. Volume buttons are not available.",
        "parameters": [
          {
            "$ref": "#/components/parameters/phone"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "enum": [
                      "home",
                      "lock"
                    ]
                  },
                  "force": {
                    "type": "boolean",
                    "default": false,
                    "description": "Act even though a person tapped this phone in the last minute."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done: the phone has carried it out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "400": {
            "description": "bad_json, bad_button.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "phone_not_found: you have no phone with that id or name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "ambiguous_phone, phone_offline, phone_busy (the farm is using the phone; force does not help) or phone_in_use (a person tapped it in the last minute; send \"force\": true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "too_large: the JSON body is over 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "501": {
            "description": "not_supported: this phone cannot do that yet; GET /api/v1/phones/{phone} lists what it can do.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "phone_error: the phone did not manage it; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "phone_unreachable or not_available: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/phones/{phone}/apps/open": {
      "post": {
        "tags": [
          "Phone control"
        ],
        "operationId": "openApp",
        "summary": "Open an app",
        "description": "Opens an app on the phone.",
        "parameters": [
          {
            "$ref": "#/components/parameters/phone"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "app"
                ],
                "properties": {
                  "app": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100,
                    "examples": [
                      "instagram",
                      "com.burbn.instagram"
                    ],
                    "description": "A name: instagram, tiktok, facebook, threads, x (or twitter), reddit, snapchat, youtube, whatsapp, telegram, safari, settings, photos, camera, files or messages. Standard phones also take any bundle id; live-sound phones also take an installed app's name as it appears on the phone."
                  },
                  "force": {
                    "type": "boolean",
                    "default": false,
                    "description": "Act even though a person tapped this phone in the last minute."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done: the phone has carried it out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "400": {
            "description": "bad_json, bad_app or unknown_app.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "phone_not_found: you have no phone with that id or name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "ambiguous_phone, phone_offline, phone_busy (the farm is using the phone; force does not help) or phone_in_use (a person tapped it in the last minute; send \"force\": true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "too_large: the JSON body is over 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "501": {
            "description": "not_supported: this phone cannot do that yet; GET /api/v1/phones/{phone} lists what it can do.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "phone_error or app_not_opened: the phone did not manage it; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "phone_unreachable or not_available: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/phones/{phone}/apps/close": {
      "post": {
        "tags": [
          "Phone control"
        ],
        "operationId": "closeApp",
        "summary": "Close an app",
        "description": "Closes an app completely. Standard phones only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/phone"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "app"
                ],
                "properties": {
                  "app": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100,
                    "examples": [
                      "instagram",
                      "com.burbn.instagram"
                    ],
                    "description": "A name: instagram, tiktok, facebook, threads, x (or twitter), reddit, snapchat, youtube, whatsapp, telegram, safari, settings, photos, camera, files or messages. Standard phones also take any bundle id; live-sound phones also take an installed app's name as it appears on the phone."
                  },
                  "force": {
                    "type": "boolean",
                    "default": false,
                    "description": "Act even though a person tapped this phone in the last minute."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done: the phone has carried it out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "400": {
            "description": "bad_json, bad_app or unknown_app.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "phone_not_found: you have no phone with that id or name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "ambiguous_phone, phone_offline, phone_busy (the farm is using the phone; force does not help) or phone_in_use (a person tapped it in the last minute; send \"force\": true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "too_large: the JSON body is over 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "501": {
            "description": "not_supported: this phone cannot do that yet; GET /api/v1/phones/{phone} lists what it can do.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "phone_error: the phone did not manage it; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "phone_unreachable or not_available: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/phones/{phone}/url": {
      "post": {
        "tags": [
          "Phone control"
        ],
        "operationId": "openUrl",
        "summary": "Open a link",
        "description": "Opens a web link on the phone. http and https only. Standard phones only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/phone"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "pattern": "^[Hh][Tt][Tt][Pp][Ss]?://\\S+$",
                    "maxLength": 2008,
                    "examples": [
                      "https://instagram.com/yourbrand"
                    ]
                  },
                  "force": {
                    "type": "boolean",
                    "default": false,
                    "description": "Act even though a person tapped this phone in the last minute."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Done: the phone has carried it out.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ok"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "400": {
            "description": "bad_json, bad_url.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "phone_not_found: you have no phone with that id or name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "ambiguous_phone, phone_offline, phone_busy (the farm is using the phone; force does not help) or phone_in_use (a person tapped it in the last minute; send \"force\": true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "too_large: the JSON body is over 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "501": {
            "description": "not_supported: this phone cannot do that yet; GET /api/v1/phones/{phone} lists what it can do.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "phone_error: the phone did not manage it; try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "phone_unreachable or not_available: try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/posts": {
      "post": {
        "tags": [
          "Posts"
        ],
        "operationId": "createPost",
        "summary": "Schedule a post",
        "description": "One call schedules one post, or the same post on several phones with `phones`. Send phone, account or phones. The answer comes right away; follow the post with its id.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "type",
                  "post_at",
                  "media"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "instagram_reel",
                      "instagram_post",
                      "tiktok",
                      "facebook_reel"
                    ]
                  },
                  "phone": {
                    "type": "string",
                    "description": "The id or name from GET /api/v1/phones."
                  },
                  "account": {
                    "type": "string",
                    "description": "The name you gave a phone on the scheduler page, with or without @."
                  },
                  "phones": {
                    "type": "string",
                    "examples": [
                      "iPhone 1, iPhone 2"
                    ],
                    "description": "Several phones, comma separated, up to 25: the same post goes to each, 8 to 10 minutes after the previous one. Used instead of phone and account when present."
                  },
                  "media": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 10,
                    "items": {
                      "type": "string",
                      "format": "binary",
                      "contentMediaType": "application/octet-stream"
                    },
                    "description": "Repeat the media part for each file. instagram_reel, tiktok and facebook_reel take exactly one MP4 or MOV video; instagram_post takes 1 to 10 photos or videos. The type is read from the bytes, not the file name. Keep the whole call under 100 MB."
                  },
                  "caption": {
                    "type": "string",
                    "maxLength": 2200
                  },
                  "post_at": {
                    "type": "string",
                    "examples": [
                      "2026-11-02T18:00:00+01:00",
                      "now"
                    ],
                    "description": "ISO 8601 with a time zone (2026-11-02T18:00:00+01:00, or ...Z for UTC), a Unix time in seconds, or the word now. At most 5 minutes in the past and 60 days ahead."
                  },
                  "max_delay_minutes": {
                    "type": "integer",
                    "default": 12,
                    "anyOf": [
                      {
                        "const": 0
                      },
                      {
                        "minimum": 8,
                        "maximum": 60
                      }
                    ],
                    "description": "Posts at a random moment 8 to this many minutes after post_at; 0 posts on time."
                  },
                  "music": {
                    "type": "string",
                    "maxLength": 100,
                    "description": "instagram_reel only: a song to find and add."
                  },
                  "hide_likes": {
                    "type": "boolean",
                    "default": false,
                    "description": "instagram_reel only. Not together with trial_reel."
                  },
                  "trial_reel": {
                    "type": "boolean",
                    "default": false,
                    "description": "instagram_reel only: post a trial reel."
                  },
                  "warmup_after": {
                    "type": "boolean",
                    "default": false,
                    "description": "Instagram reels and posts: a 14 to 18 minute Instagram warm-up on the phone after the post. TikTok and Facebook reels ignore it."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Scheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostCreated"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "400": {
            "description": "bad_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, bad_content_type (no multipart boundary) or invalid_request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account. phone_not_yours: that phone is not assigned to you.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "phone_not_found or account_not_found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "ambiguous_phone, or phone_cannot_post: a live-sound phone that cannot post this type yet (see post_types in GET /api/v1/phones).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "411": {
            "description": "length_required: the request has no body.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "too_large: the upload is too big.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "bad_content_type: send the post as multipart/form-data.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "description": "starting_up: the farm is starting up; try again in a minute.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Posts"
        ],
        "operationId": "listPosts",
        "summary": "List your posts",
        "description": "Newest scheduled time first, including posts made on the website (see source). Finished posts stay listed for 24 hours, failed ones for 7 days.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "scheduled",
                "posting",
                "posted",
                "failed",
                "cancelled"
              ]
            }
          },
          {
            "name": "phone",
            "in": "query",
            "description": "A phone id or name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "description": "Scheduled at or after this time. ISO 8601 with a time zone (2026-11-02T18:00:00+01:00, or ...Z for UTC), a Unix time in seconds, or the word now.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "description": "Scheduled at or before this time. ISO 8601 with a time zone (2026-11-02T18:00:00+01:00, or ...Z for UTC), a Unix time in seconds, or the word now.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Your posts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostList"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "400": {
            "description": "bad_status, bad_since or bad_until.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "phone_not_found: you have no phone with that id or name.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "ambiguous_phone: send the phone id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/posts/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/postId"
        }
      ],
      "get": {
        "tags": [
          "Posts"
        ],
        "operationId": "getPost",
        "summary": "Check a post",
        "description": "Its status moves from scheduled to posting to posted, or to failed with the reason in result.",
        "responses": {
          "200": {
            "description": "The post.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "post_not_found: you have no post with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "patch": {
        "tags": [
          "Posts"
        ],
        "operationId": "updatePost",
        "summary": "Change a post",
        "description": "New caption or time, while the post is still scheduled. A new post_at gets a new random delay like a new post (max_delay_minutes, default 12; 0 posts on time).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PostChange"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The changed post.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "400": {
            "description": "bad_json, unknown_field, nothing_to_change, bad_post_at, post_at_in_past, post_at_too_far, bad_max_delay or caption_too_long.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "post_not_found: you have no post with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "not_scheduled: only scheduled posts can be changed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "too_large: the JSON body is over 64 KB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      },
      "delete": {
        "tags": [
          "Posts"
        ],
        "operationId": "deletePost",
        "summary": "Cancel or remove a post",
        "description": "A scheduled post is cancelled and its media deleted (send it again to redo it). A posted, failed or cancelled post is removed from your list. A post being published cannot be cancelled.",
        "responses": {
          "200": {
            "description": "The cancelled post, or {\"id\", \"deleted\": true} when it was removed.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Post"
                    },
                    {
                      "$ref": "#/components/schemas/PostRemoved"
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "post_not_found: you have no post with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "already_posting: it is being published right now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    },
    "/api/v1/posts/{id}/retry": {
      "parameters": [
        {
          "$ref": "#/components/parameters/postId"
        }
      ],
      "post": {
        "tags": [
          "Posts"
        ],
        "operationId": "retryPost",
        "summary": "Retry a failed post",
        "description": "Runs a failed post again on the same phone, while its media is still kept (24 hours).",
        "responses": {
          "200": {
            "description": "The post, scheduled again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            },
            "headers": {
              "X-API-Key-Expires": {
                "$ref": "#/components/headers/X-API-Key-Expires"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "ip_not_allowed, account_disabled or key_not_allowed: the key works, but not from here or not for this account. phone_not_yours: that phone is no longer assigned to you.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "post_not_found: you have no post with that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "not_failed: only failed posts can be retried.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "media_gone: the media was deleted 24 hours after the post finished; send the post again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Your API key (mky_...): Authorization: Bearer YOUR_KEY. Send a User-Agent that names your software, e.g. YourCompany/1.0: Python urllib's default one is refused by our network."
      }
    },
    "headers": {
      "X-API-Key-Expires": {
        "description": "When your key expires (UTC).",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before the next call.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      }
    },
    "parameters": {
      "phone": {
        "name": "phone",
        "in": "path",
        "required": true,
        "description": "The phone id, or its name from GET /api/v1/phones, URL-encoded (iPhone%201).",
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 100
        }
      },
      "postId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The post id.",
        "schema": {
          "type": "string",
          "pattern": "^[A-Za-z0-9-]{8,64}$"
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "missing_key, invalid_key, invalid_token (a sign-in token: those work on /mcp only), key_expired, key_disabled or key_revoked.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "rate_limited: more than 60 calls in a minute.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ServerError": {
        "description": "server_error: something went wrong on our side; try again.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "A stable code, e.g. post_at_in_past."
          },
          "message": {
            "type": "string",
            "description": "What to fix, in plain words."
          }
        },
        "examples": [
          {
            "error": "post_at_in_past",
            "message": "post_at is 12 min in the past. Send a future time, or now."
          }
        ]
      },
      "Me": {
        "type": "object",
        "required": [
          "user",
          "key",
          "limits"
        ],
        "properties": {
          "user": {
            "type": "string",
            "description": "Your MKY Remote username."
          },
          "key": {
            "type": "object",
            "required": [
              "id",
              "prefix",
              "created",
              "expires",
              "days_left"
            ],
            "properties": {
              "id": {
                "type": "string",
                "examples": [
                  "k_1a2b3c4d"
                ]
              },
              "prefix": {
                "type": "string",
                "description": "The first characters of the key.",
                "examples": [
                  "mky_Ab12"
                ]
              },
              "created": {
                "type": "string",
                "format": "date-time"
              },
              "expires": {
                "type": "string",
                "format": "date-time"
              },
              "days_left": {
                "type": "integer",
                "minimum": 0
              }
            }
          },
          "limits": {
            "type": "object",
            "required": [
              "calls_per_minute"
            ],
            "properties": {
              "calls_per_minute": {
                "type": "integer",
                "examples": [
                  60
                ]
              }
            }
          }
        }
      },
      "Phone": {
        "type": "object",
        "required": [
          "id",
          "name",
          "online",
          "automation",
          "rental_expires"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Never changes."
          },
          "name": {
            "type": "string",
            "description": "\"iPhone N\" in your own numbering, or the name you gave the phone. The numbering can shift when phones are added or removed."
          },
          "online": {
            "type": "boolean"
          },
          "automation": {
            "type": "boolean",
            "description": "Whether the phone can post by itself."
          },
          "rental_expires": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "engine": {
            "type": "string",
            "enum": [
              "standard",
              "live-sound"
            ]
          },
          "control": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "screenshot",
                "screen",
                "tap",
                "swipe",
                "type",
                "button",
                "apps/open",
                "apps/close",
                "url"
              ]
            },
            "description": "The control calls this phone takes."
          },
          "post_types": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "instagram_reel",
                "instagram_post",
                "tiktok",
                "facebook_reel"
              ]
            },
            "description": "Only on live-sound phones: the post types they can post. Other phones take every type."
          }
        }
      },
      "PhoneList": {
        "type": "object",
        "required": [
          "phones"
        ],
        "properties": {
          "phones": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Phone"
            }
          }
        }
      },
      "PhoneStatus": {
        "type": "object",
        "required": [
          "id",
          "name",
          "engine",
          "online",
          "can"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "engine": {
            "type": "string",
            "enum": [
              "standard",
              "live-sound"
            ]
          },
          "online": {
            "type": "boolean"
          },
          "can": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "screenshot",
                "screen",
                "tap",
                "swipe",
                "type",
                "button",
                "apps/open",
                "apps/close",
                "url"
              ]
            }
          },
          "screen": {
            "type": "object",
            "description": "Standard phones that are online.",
            "required": [
              "width",
              "height",
              "unit"
            ],
            "properties": {
              "width": {
                "type": "integer"
              },
              "height": {
                "type": "integer"
              },
              "unit": {
                "type": "string",
                "const": "points"
              }
            }
          }
        }
      },
      "ScreenText": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "text",
                "x",
                "y",
                "width",
                "height",
                "center_x",
                "center_y",
                "confidence"
              ],
              "properties": {
                "text": {
                  "type": "string"
                },
                "x": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1,
                  "description": "Left edge of the text."
                },
                "y": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1,
                  "description": "Top edge of the text."
                },
                "width": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                },
                "height": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                },
                "center_x": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1,
                  "description": "Tap here."
                },
                "center_y": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1,
                  "description": "Tap here."
                },
                "confidence": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1
                }
              }
            }
          }
        }
      },
      "Ok": {
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          }
        }
      },
      "Post": {
        "type": "object",
        "required": [
          "id",
          "status",
          "type",
          "phone",
          "caption",
          "scheduled_for",
          "started_at",
          "finished_at",
          "result",
          "media",
          "options",
          "source",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "scheduled",
              "posting",
              "posted",
              "failed",
              "cancelled"
            ],
            "description": "scheduled (also while it waits to retry), posting, posted, failed or cancelled."
          },
          "type": {
            "type": "string",
            "examples": [
              "instagram_reel",
              "instagram_post",
              "tiktok",
              "facebook_reel"
            ],
            "description": "One of the four API types; posts made on the website can show other types."
          },
          "phone": {
            "type": "object",
            "required": [
              "id",
              "name"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              }
            }
          },
          "caption": {
            "type": "string"
          },
          "scheduled_for": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "finished_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "result": {
            "type": [
              "string",
              "null"
            ],
            "description": "What happened, e.g. why it failed, or \"Retrying after: ...\" before the automatic retry."
          },
          "media": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "name"
              ],
              "properties": {
                "name": {
                  "type": "string"
                }
              }
            }
          },
          "options": {
            "type": "object",
            "required": [
              "music",
              "hide_likes",
              "trial_reel",
              "warmup_after"
            ],
            "properties": {
              "music": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "hide_likes": {
                "type": "boolean"
              },
              "trial_reel": {
                "type": "boolean"
              },
              "warmup_after": {
                "type": "boolean"
              }
            }
          },
          "source": {
            "type": "string",
            "examples": [
              "api",
              "web"
            ],
            "description": "api, web (the scheduler page) or vault (the media library)."
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "batch": {
            "type": "object",
            "description": "Posts sent to several phones in one call.",
            "required": [
              "size",
              "index"
            ],
            "properties": {
              "size": {
                "type": "integer"
              },
              "index": {
                "type": "integer",
                "description": "0 for the first phone."
              }
            }
          }
        }
      },
      "PostCreated": {
        "type": "object",
        "required": [
          "id",
          "status",
          "count",
          "posts_between",
          "posts"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The first post (the first phone)."
          },
          "status": {
            "type": "string",
            "enum": [
              "scheduled",
              "posting",
              "posted",
              "failed",
              "cancelled"
            ]
          },
          "count": {
            "type": "integer",
            "description": "One post per phone."
          },
          "posts_between": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date-time"
            },
            "minItems": 2,
            "maxItems": 2,
            "description": "When the first post goes out: earliest and latest."
          },
          "posts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Post"
            }
          }
        }
      },
      "PostList": {
        "type": "object",
        "required": [
          "posts",
          "count",
          "total"
        ],
        "properties": {
          "posts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Post"
            }
          },
          "count": {
            "type": "integer",
            "description": "Posts in this answer."
          },
          "total": {
            "type": "integer",
            "description": "Posts that match, before limit."
          }
        }
      },
      "PostRemoved": {
        "type": "object",
        "required": [
          "id",
          "deleted"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "deleted": {
            "type": "boolean",
            "const": true
          }
        }
      },
      "PostChange": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "properties": {
          "caption": {
            "type": "string",
            "maxLength": 2200
          },
          "post_at": {
            "type": [
              "string",
              "number"
            ],
            "description": "ISO 8601 with a time zone (2026-11-02T18:00:00+01:00, or ...Z for UTC), a Unix time in seconds, or the word now."
          },
          "max_delay_minutes": {
            "type": "integer",
            "anyOf": [
              {
                "const": 0
              },
              {
                "minimum": 8,
                "maximum": 60
              }
            ],
            "description": "Only together with post_at."
          }
        },
        "examples": [
          {
            "caption": "New text",
            "post_at": "2026-11-03T18:00:00+01:00"
          }
        ]
      }
    }
  }
}
