该API为云机内部提供出来的服务,可通过该API接口直接控制云机,执行一些自动化操作,比如:获取屏幕和UI元素、点击和长按、文本输入和键盘、滑动和滚动、应用与页面导航、等待与元素读取等
接入前提:在 DuoPlus 控制台“自动化”→“API”获取 API Key。目标云机须已开机(status == 1),且 HTTP 服务已启用(http_status 为 1 或 "1")。若云机尚未开机,先通过控制台或平台开机接口启动;本文件不包含开机接口。Gateway 使用该 API Key 作为 Bearer Token;部署时须确保设备侧鉴权与该 Key 匹配,否则可能返回 401。
调用 HTTP Gateway 前,必须先通过平台 list 接口取得目标云机的 ip 和 region,并确认 status == 1、http_status 为 1。
POST https://openapi.duoplus.cn/api/v1/cloudPhone/list
请求头:
Content-Type: application/json
Lang: zh
DuoPlus-API-Key: <API_KEY>
按云机 ID 精确查询:
{
"image_id": ["IMAGE_ID"],
"page": 1,
"pagesize": 100
}
也可按名称查询:
{
"name": "phone-name",
"page": 1,
"pagesize": 100
}
| 参数 | 必填 | 说明 |
|---|---|---|
page |
否 | 页码,从 1 开始;不传默认第 1 页 |
pagesize |
否 | 每页数量,最大 100;不传默认 10 |
image_id |
否 | 云机 ID 数组,推荐使用精确 ID |
name |
否 | 云机名称;名称可能重复 |
link_status |
否 | 连接状态数组 |
group_id |
否 | 分组 ID |
proxy_id |
否 | 代理 ID |
响应包络示意:
{
"code": 200,
"message": "success",
"data": {
"list": [
{
"id": "IMAGE_ID",
"name": "phone-name",
"status": 1,
"ip": "10.0.0.10",
"region": "sg",
"http_status": 1
}
],
"total_page": 1
}
}
上例只展示自动化需要的关键字段,服务端可能返回更多设备信息。
| 字段 | 用途 |
|---|---|
data.list[].id |
云机 ID,用于确认选中了正确设备 |
data.list[].status |
1 表示云机已开机;其他状态需先处理 |
data.list[].ip |
作为 Gateway 请求头 CloudIP |
data.list[].region |
作为 Gateway 请求头 Region;以选中云机的实际返回值为准 |
data.list[].http_status |
1/"1" 表示 HTTP 服务已启用;0/"0" 表示未启用 |
data.total_page |
总页数;未按 ID 精确查询时需要遍历全部分页 |
成功必须同时满足 HTTP 请求成功和响应顶层 code == 200。
curl -sS 'https://openapi.duoplus.cn/api/v1/cloudPhone/list' \
-H 'Content-Type: application/json' \
-H 'Lang: zh' \
-H "DuoPlus-API-Key: $DUOPLUS_API_KEY" \
--data '{"image_id":["IMAGE_ID"],"page":1,"pagesize":100}'
核对匹配项的 id 后,从该项提取:
CloudIP = matched_phone.ip
Region = matched_phone.region
只有 status == 1 且 String(http_status) == "1" 时才能继续调用自动化 Gateway。ip 不为空并不代表自动化已启用。若按名称查找,应遍历 total_page 的所有分页,并在名称重复时用 ID 确认目标。平台 OpenAPI 每个接口限 1 QPS;分页请求也应控制频率。
所有自动化操作均请求:
POST https://agent-gateway.duoplus.cn/agent-command
Content-Type: application/json
Authorization: Bearer <API_KEY>
Region: <REGION>
CloudIP: <CLOUD_IP>
| 请求头 | 说明 |
|---|---|
Authorization |
Gateway 鉴权,格式为 Bearer <API_KEY> |
Region |
目标云机 list 返回的 region |
CloudIP |
云机私网 IP |
云机必须已运行并启用 HTTP 服务。Gateway 返回 401 时,应检查 API Key 与设备鉴权配置,不要盲目重试。第 13 节 cURL 中的区域和 IP 示例值必须替换为目标云机的实际返回值。
operation |
用途 |
|---|---|
health |
检查 Gateway/后端健康状态 |
ready |
检查自动化执行器是否就绪 |
submit |
获取屏幕/UI 或执行 UI 动作 |
query |
查询已保留的命令结果 |
stop |
停止正在运行或卡住的任务 |
执行动作的通用请求:
{
"operation": "submit",
"command_id": "click-element-1730000000000-a1b2c3d4",
"task_id": "click-element-task-1730000000000-a1b2c3d4",
"action": "execute",
"payload": {
"task_type": "ai",
"task_id": "click-element-task-1730000000000-a1b2c3d4",
"action": "execute",
"action_name": "CLICK_ELEMENT",
"params": {
"text": "Search",
"wait_after": 500
}
}
}
command_id 与 task_id 应唯一。action_name 使用大写动作名。payload.params。deadline_at。任意动作的 params 均可带:
| 参数 | 说明 |
|---|---|
wait_before |
动作前等待时间,毫秒 |
wait_after |
动作后等待时间,毫秒;导航/加载建议 500–1500 |
健康检查:
{"operation":"health"}
就绪检查:
{"operation":"ready"}
响应满足 executor == "ready" 或 ready == true 即表示执行器就绪。收到 503 或 NOT_READY 时,可在启动超时内继续轮询。
{
"operation": "submit",
"command_id": "ui-state-1730000000000-a1b2c3d4",
"task_id": "ui-state-task-1730000000000-a1b2c3d4",
"action": "get_ui_state",
"payload": {
"task_type": "ai",
"task_id": "ui-state-task-1730000000000-a1b2c3d4",
"action": "get_ui_state",
"lang": "zh"
}
}
result_json 是 JSON 字符串,需要二次解析:
const outer = await response.json();
const result = JSON.parse(outer.result_json);
解析后的对象可能包含:
success:动作是否成功;screenshot:Base64 编码的当前屏幕截图。截图解码:
const encoded = result.screenshot.replace(/^data:[^,]+,/, "");
const bytes = Buffer.from(encoded, "base64");
第一次操作前读取 UI;每次点击、输入、滑动或导航后再次读取,以真实屏幕变化验证结果。
CLICK_ELEMENT{"text":"Continue","wait_after":800}
| 参数 | 说明 |
|---|---|
resource_id |
Android 资源 ID,通常最稳定 |
content_desc |
无障碍内容描述 |
text |
元素显示文本 |
class_name |
Android 控件类名 |
element_order |
多个元素匹配时的序号,从 0 开始 |
{"resource_id":"com.example:id/login"}
{"content_desc":"Search"}
{"text":"Item","element_order":1}
优先选择:resource_id → content_desc/text → 坐标。
LONG_ELEMENT{"text":"Message","duration":1200}
选择字段同 CLICK_ELEMENT,duration 单位为毫秒。
点击 CLICK_COORDINATE:
{"x":500,"y":420,"wait_after":500}
长按 LONG_COORDINATE:
{"x":500,"y":420,"duration":1200}
双击 DOUBLE_TAP_COORDINATE:
{"x":500,"y":420}
坐标为 0..1000 的相对值,左上角 (0,0),右下角 (1000,1000):
relative_x = round(pixel_x / screenshot_width * 1000)
relative_y = round(pixel_y / screenshot_height * 1000)
坐标动作前应立即截图并取目标中心;页面跳转或旋转后不能复用旧坐标。
INPUT_CONTENT{"content":"hello world","clear_first":true}
输入前必须先聚焦输入框:获取 UI → 点击输入框 → INPUT_CONTENT → 必要时发送 Enter → 再次获取 UI 验证。
KEYBOARD_OPERATION{"key":"enter"}
支持:enter、delete、tab、escape、space。
动作名:SLIDE_PAGE。
默认手势:
{"direction":"up","wait_after":600}
支持 up、down、left、right。向下浏览内容时,手指向上滑,因此使用 up。
自定义轨迹:
{
"direction": "up",
"start_x": 520,
"start_y": 780,
"end_x": 500,
"end_y": 260,
"wait_after": 600
}
先尝试默认手势;每次滑动后重新读取屏幕。连续无效时轻微调整轨迹,同一目标通常最多尝试 3 次。
打开应用 OPEN_APP:
{"package_name":"com.android.settings","wait_after":1200}
返回桌面 GO_TO_HOME:
{}
返回上一页 PAGE_BACK:
{}
固定等待 WAIT_TIME:
{"wait_time":1500}
等待元素 WAIT_FOR_SELECTOR:
{"resource_id":"com.example:id/result","timeout":15}
或:
{"text":"Completed","timeout":10}
timeout 默认 10 秒。
读取单个元素文本 GET_SINGLE_ELEMENT_TEXT:
{"resource_id":"com.example:id/title"}
可使用 text、resource_id、class_name 等选择字段。
查询命令结果:
{"operation":"query","command_id":"<COMMAND_ID>"}
停止卡住的任务:
{
"operation": "stop",
"command_id": "stop-1730000000000-a1b2c3d4",
"task_id": "<TASK_ID>",
"reason": "operation timeout"
}
stop 仅用于长时间运行或卡住的任务,不用于正常完成操作。
对 submit 的 UI 动作,不能仅根据 HTTP 200 判断成功。必须同时满足:
state == "SUCCEEDED";result_json.success == true;const outer = await response.json();
if (outer.state !== "SUCCEEDED") throw new Error("Gateway command failed");
const result = JSON.parse(outer.result_json);
if (result.success !== true) throw new Error("UI action failed");
// 再次调用 get_ui_state,验证真实界面结果。
curl -sS 'https://agent-gateway.duoplus.cn/agent-command' \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $DUOPLUS_API_KEY" \
-H "Region: $DUOPLUS_REGION" \
-H "CloudIP: $DUOPLUS_CLOUD_IP" \
--data '{
"operation":"submit",
"command_id":"ui-state-UNIQUE_ID",
"task_id":"ui-state-task-UNIQUE_ID",
"action":"get_ui_state",
"payload":{
"task_type":"ai",
"task_id":"ui-state-task-UNIQUE_ID",
"action":"get_ui_state",
"lang":"zh"
}
}'
curl -sS 'https://agent-gateway.duoplus.cn/agent-command' \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $DUOPLUS_API_KEY" \
-H "Region: $DUOPLUS_REGION" \
-H "CloudIP: $DUOPLUS_CLOUD_IP" \
--data '{
"operation":"submit",
"command_id":"click-UNIQUE_ID",
"task_id":"click-task-UNIQUE_ID",
"action":"execute",
"payload":{
"task_type":"ai",
"task_id":"click-task-UNIQUE_ID",
"action":"execute",
"action_name":"CLICK_ELEMENT",
"params":{"text":"Continue","wait_after":800}
}
}'
执行第 13 节示例前,设置 DUOPLUS_API_KEY、DUOPLUS_REGION、DUOPLUS_CLOUD_IP,并将每次请求中重复出现的 UNIQUE_ID 换成新的唯一值;同一次请求的顶层和 payload 中的 task_id 必须一致。示例为 Bash 语法;PowerShell 中环境变量写法不同。
500–1500 ms 的 wait_after。