该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。动作名:SHELL_COMMAND。
该动作在目标云机中执行一条非交互式 Shell 命令,并在命令成功时通过
result_json.result_text 返回标准输出。它适合读取系统属性、查询包信息和执行经过审核的
设备维护命令。
| 参数 | 必填 | 说明 |
|---|---|---|
command |
是 | 要执行的非空 Shell 命令 |
need_screenshot |
否 | 是否在命令执行后返回截图,默认 true;纯命令操作建议传 false |
wait_before |
否 | 执行命令前等待的时间,单位毫秒 |
wait_after |
否 | 执行命令后等待的时间,单位毫秒 |
参数示例:
{
"command": "getprop ro.build.version.release",
"need_screenshot": false
}
当前设备执行器对单条 Shell 命令的内部等待时间约为 20 秒,不支持通过请求参数修改。
不要提交交互式命令、持续运行的前台进程或无法在超时时间内自行结束的命令。
UNIQUE_ID="$(date +%s%3N)-$RANDOM"
curl --max-time 30 -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\": \"shell-${UNIQUE_ID}\",
\"task_id\": \"shell-task-${UNIQUE_ID}\",
\"action\": \"execute\",
\"payload\": {
\"task_type\": \"ai\",
\"task_id\": \"shell-task-${UNIQUE_ID}\",
\"action\": \"execute\",
\"action_name\": \"SHELL_COMMAND\",
\"params\": {
\"command\": \"getprop ro.build.version.release\",
\"need_screenshot\": false
}
}
}"
每次请求必须生成新的 command_id 和 task_id;同一次请求的顶层 task_id 与
payload.task_id 必须一致。
Gateway 同步返回命令终态。外层响应示意:
{
"command_id": "shell-1730000000000-1234",
"task_id": "shell-task-1730000000000-1234",
"state": "SUCCEEDED",
"success": true,
"message": "Execute shell command: getprop ro.build.version.release - Success",
"result_json": "{\"task_id\":\"shell-task-1730000000000-1234\",\"success\":true,\"message\":\"Execute shell command: getprop ro.build.version.release - Success\",\"result_text\":\"15\\n\",\"screenshot\":\"\",\"current_app\":\"\"}"
}
result_json 是 JSON 字符串,需要二次解析:
const outer = await response.json();
if (outer.state !== "SUCCEEDED" || outer.success !== true) {
throw new Error(outer.message || "Shell command failed");
}
const result = JSON.parse(outer.result_json);
if (result.success !== true) {
throw new Error(result.message || "Shell command failed");
}
console.log(result.result_text); // 命令成功时的标准输出
成功必须同时满足:
state == "SUCCEEDED";success == true;result_json.success == true。result_text 只保证包含命令成功时捕获到的标准输出。命令失败时不要依赖它获取 stderr,
应以 success 和 message 判断结果。不要执行会产生大量输出的命令,以免超过 Gateway/Binder
结果大小限制。
SHELL_COMMAND 具备高权限和高风险。调用方必须遵守以下规则:
"need_screenshot": false,仅在确实需要验证界面变化时请求截图。建议后端针对 SHELL_COMMAND 单独配置权限范围、调用审计、速率限制和命令白名单,而不是仅
依赖通用 Gateway Bearer Token。