# 聚浪 Auto 公开 API v1

`/v1` 是供第三方系统、CLI 和 AI 适配器使用的稳定边界。`/api/admin/*`、`/api/console/*`、`/api/device/*` 都是内部实现，不属于开放协议，不应由外部客户端调用。

- 生产 Base URL：`https://api.auto.julang.cn`
- 开发者中心：`https://console.auto.julang.cn/console#docs`
- 交互文档：`https://console.auto.julang.cn/developers/openapi`
- OpenAPI：`https://api.auto.julang.cn/v1/openapi.json`
- MCP：`https://api.auto.julang.cn/mcp`

## 局域网与广域网

同一套 `/v1` 协议可运行在两种网络模式：

- 局域网：将 `PUBLIC_API_BASE_URL` 设为内网 HTTPS 域名（推荐）或服务器地址，手机与调用方都连到该服务。不要把 Uvicorn 端口直接暴露给不可信网段。
- 广域网：使用 `https://api.auto.julang.cn`，由 Nginx/TLS 反向代理进入服务。手机只需主动出站建立 HTTPS/WebSocket，不需要公网 IP 或穿透到手机。

无论哪种模式，都必须保留 Bearer 鉴权、设备归属和授权检查。广域网必须使用 HTTPS；局域网若无法配置可信证书，应通过 VPN/零信任网关连接，不要关闭令牌鉴权。

## 5 分钟快速开始

1. 在 `https://console.auto.julang.cn/console` 登录，进入“账号管理 → 创建访问令牌”。
2. 勾选 `devices:read`、`capabilities:read`、`tasks:write`、`tasks:read`、`logs:read`。内部脚本测试还需勾选 `internal:execute`。令牌明文仅显示一次。
3. 确保手机已登录同一账号、已绑定、设备授权有效且自动化服务已就绪。
4. 设置环境变量，列出设备并记下 `id`：

```bash
export JULANG_TOKEN='jlk_...'
curl -sS https://api.auto.julang.cn/v1/devices \
  -H "Authorization: Bearer $JULANG_TOKEN"
```

5. 执行一个安全能力：

```bash
curl -sS https://api.auto.julang.cn/v1/tasks \
  -X POST \
  -H "Authorization: Bearer $JULANG_TOKEN" \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: demo-$(uuidgen)" \
  -d '{
    "deviceId":"YOUR_DEVICE_ID",
    "operation":"capability.run",
    "capability":{"id":"device.info","args":{}},
    "confirmation":false
  }'
```

记下返回的 `task_...`，调用 `GET /v1/tasks/{id}` 直到状态进入 `succeeded`、`failed` 或 `cancelled`。

## Bearer 令牌与 scope

请使用 `Authorization: Bearer jlk_...`。登录会话 `jla_...`、管理员令牌和设备令牌不能作为公开 API 凭证。

| Scope | 允许的操作 |
|---|---|
| `devices:read` | `GET /v1/devices`；MCP `devices_list`、`device_get` |
| `capabilities:read` | `GET /v1/capabilities`；MCP `capabilities_list` |
| `tasks:write` | 创建、停止任务；MCP `workflow_validate` |
| `tasks:read` | 读取任务状态和结果 |
| `logs:read` | 读取任务进度和结果日志 |
| `internal:execute` | 在自己的设备上执行可信内部 JavaScript；需同时具有 `tasks:write` |

按最小权限为不同集成分别创建令牌。不要将令牌写入移动客户端、浏览器代码、仓库或日志；泄漏后立即在控制台撤销。

## 版本策略

`/v1` 只增加向后兼容的字段、枚举值和功能；客户端必须忽略未识别字段。删除/改名字段、改变含义或改变鉴权模型会使用新的主版本路径。

## 完整生命周期

### 1. 设备

`GET /v1/devices` 只返回令牌所属账号绑定的设备。返回值刻意移除 UDID、Android ID、原始心跳 metadata 和当前前台 App。创建任务前应确认 `online=true`、`automationReady=true`、`licensed=true`。新版执行器同时返回 `runtimeVersion`、`protocolVersions`、`capabilitySetVersion`、`supportedCapabilities`、`maxScriptBytes` 和 `maxExecutionMs`。

### 2. 能力

`GET /v1/capabilities?platform=android` 按平台过滤；也可传 `deviceId`，由服务端使用设备的真实平台。每个能力提供 `id`、`args`、`platform` 和 `risk`。

### 3. 创建任务

`POST /v1/tasks` 支持三种操作：

- `capability.run`：调用服务端能力目录。
- `workflow.run`：先白名单校验工作流节点，再针对 Android/iOS 设备编译。
- `script.run`：内部测试用的可信 JavaScript 执行入口，需要额外的 `internal:execute` scope。

`workflow.run` 明确拒绝 `script`、`capture`、`protocol`、`protocolVersion` 和 `sourceWorkflow` 等内部字段；原始代码只能通过 `script.run` 下发。

每次必须带 8–128 字符的 `Idempotency-Key`。同一账号下，同一键+同一请求只创建一次，重放返回 `Idempotency-Replayed: true`；同一键换了请求体返回 `409 idempotency_conflict`。

### 4. 状态和结果

`GET /v1/tasks/{id}` 返回：

`queued → running → succeeded | failed`，或 `queued/running → cancelled`。

发布后的客户端建议 1s、2s、4s、8s 指数退避轮询，之后最多每 15s 一次。请勿重复创建任务来代替状态查询。

### 5. 停止

`POST /v1/tasks/{id}/cancel` 可安全重复调用。排队任务不再执行；运行中任务向 Android WebSocket/轮询客户端或 iOS 轮询客户端下发协作式中断。当前步骤如果已经发生外部副作用，取消不会自动回滚它。

### 6. 日志

`GET /v1/tasks/{id}/logs` 返回结构化的 `task.created`、`task.progress`、`task.result` 事件。结果日志不会回传完整屏幕内容或设备标识符。

## 工作流例子

```json
{
  "deviceId": "YOUR_DEVICE_ID",
  "operation": "workflow.run",
  "workflow": {
    "title": "打开设置",
    "nodes": [
      {"type":"trigger","title":"手动触发","op":{"kind":"log","text":"start"}},
      {"type":"app","title":"打开设置","op":{"kind":"openApp","app":"设置"}}
    ]
  },
  "confirmation": false
}
```

## 内部 JavaScript 执行

`script.run` 让 Android/iOS 作为远程 JS 执行器。代码最大 256KB，默认超时 60 秒、最大 5 分钟；参数通过约定变量 `__JULANG_ARGS__` 提供。当前内部测试阶段的 `networkPolicy` 会随任务下发并进入审计，端侧网络 API 仍由各平台 JS Runtime 实现。

```bash
curl -sS https://api.auto.julang.cn/v1/tasks \
  -X POST \
  -H "Authorization: Bearer $JULANG_TOKEN" \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: script-$(uuidgen)" \
  -d '{
    "deviceId":"YOUR_DEVICE_ID",
    "operation":"script.run",
    "script":{
      "language":"javascript",
      "code":"return {ok:true,model:device.getModel(),input:__JULANG_ARGS__};",
      "args":{"requestId":"internal-test-1"},
      "timeoutMs":60000,
      "permissions":["ui.control","network.access"],
      "networkPolicy":{"mode":"unrestricted"}
    }
  }'
```

允许的权限声明包括 `ui.control`、`screen.read`、`files.read`、`files.write`、`storage.access`、`network.access`、`system.control`、`identifiers.read` 和 `runtime.control`。权限声明是执行意图与审计信息；内部测试结束前应在端侧增加逐 API 强制隔离。

## JavaScript

```js
const base = "https://api.auto.julang.cn";
const token = process.env.JULANG_TOKEN;
const headers = { Authorization: `Bearer ${token}`, "Content-Type": "application/json" };

const devices = await fetch(`${base}/v1/devices`, { headers }).then(r => r.json());
const response = await fetch(`${base}/v1/tasks`, {
  method: "POST",
  headers: { ...headers, "Idempotency-Key": crypto.randomUUID() },
  body: JSON.stringify({
    deviceId: devices.data[0].id,
    operation: "capability.run",
    capability: { id: "device.info", args: {} },
    confirmation: false
  })
});
if (!response.ok) throw new Error(JSON.stringify(await response.json()));
console.log(await response.json());
```

## Python

```python
import os, uuid, requests

base = "https://api.auto.julang.cn"
headers = {"Authorization": f"Bearer {os.environ['JULANG_TOKEN']}"}
device = requests.get(f"{base}/v1/devices", headers=headers, timeout=15).json()["data"][0]
task = requests.post(
    f"{base}/v1/tasks",
    headers={**headers, "Idempotency-Key": str(uuid.uuid4())},
    json={
        "deviceId": device["id"], "operation": "capability.run",
        "capability": {"id": "device.info", "args": {}}, "confirmation": False,
    },
    timeout=15,
)
task.raise_for_status()
print(task.json())
```

## 错误、限流和重试

错误统一为：

```json
{"error":{"code":"confirmation_required","message":"...","requestId":"req_...","details":{}}}
```

| HTTP | 常见 code | 处理 |
|---|---|---|
| 400 | `invalid_idempotency_key`, `invalid_platform` | 修正请求，不要盲目重试 |
| 401 | `unauthorized`, `token_expired` | 更换令牌 |
| 403 | `insufficient_scope`, `device_license_required` | 修正 scope 或设备授权 |
| 404 | `device_not_found`, `task_not_found` | 不要泄露其他账号资源；当作不存在 |
| 409 | `confirmation_required` | 展示给人工确认，不得由 AI 自行改为 `true` |
| 409 | `idempotency_conflict`, `device_offline` | 换键或等待设备上线 |
| 422 | `workflow_not_supported`, `workflow_reserved_field`, `capability_not_supported`, `script_too_large` | 修正工作流、脚本或平台 |
| 429 | `rate_limit_exceeded` | 遵守 `Retry-After`，指数退避加抖动 |
| 503 | `service_unavailable` | 使用原幂请求重试 |

默认每个令牌每分钟 60 次读、20 次写，返回 `X-RateLimit-*` 头。多实例部署时应在 API Gateway/Redis 层增加全局限流；应用内限流是最后一道单实例保护。

GET 和带原 `Idempotency-Key` 的 POST 可在网络错误、429、502、503、504 时重试。其他 4xx 不应自动重试。

## 安全操作和人工确认

- 只调用当前账号绑定、有效授权的设备。
- `risk != safe` 的能力和包含 `dangerous` 节点的工作流要求 `confirmation=true`。
- AI 只能转述即将执行的设备、App、操作、数据和可能副作用，等待用户明确同意；不能根据之前的模糊意图代替确认。
- 发送消息、发布内容、付款、删除数据、修改账号/系统设置等高风险操作应在设备端再次确认，并保留审计记录。
- `script.run` 只向内部或明确受信任的令牌授予；普通集成应使用 `capability.run` 或 `workflow.run`。
- 服务端记录脚本 SHA-256，不在审计详情中复制完整源码。
- 任务创建和停止会记入 `audit_logs`，包含用户、令牌类型、request id、IP、User-Agent 和资源标识。
