# 自动化 API

> 通过美客浏览器本地 HTTP API 管理店铺、节点、团队成员与凭据，启动浏览器并连接 CDP 完成网页自动化。

Source: https://merca.getchrovia.com/zh/docs/automation-api

美客浏览器自动化 API 用于管理店铺、网络节点、团队成员和已保存凭据，并启动店铺浏览器。

网页读取、点击、输入、截图等操作不属于这些 HTTP 接口，需要在启动浏览器后通过 CDP 或支持 CDP 的自动化工具完成。

## 1. 接入准备

1. 启动美客浏览器桌面客户端并登录账号。
2. 确认账号拥有付费套餐。
3. 在客户端设置中启用自动化 API，复制 API Key，确认监听端口。
4. 启动店铺前，确保浏览器内核已就绪，店铺已绑定可用节点。
5. 使用期间保持美客浏览器桌面客户端运行，并保持网络可用。接口复用客户端的登录状态，不提供独立的登录接口。

默认连接信息：

| 项目 | 默认值 |
| --- | --- |
| HTTP 基础地址 | `http://127.0.0.1:43210` |
| 自动化服务 | 默认关闭 |
| 监听地址 | 默认 `127.0.0.1`；允许远程访问后为 `0.0.0.0` |
| 端口范围 | `1`–`65535`，可在设置中修改 |

远程访问时，将 `127.0.0.1` 替换为运行美客浏览器的电脑地址。浏览器进程、调试端口及接口返回的本地文件路径始终属于运行美客浏览器的电脑，而不是调用方电脑。

## 2. 通用约定

### 2.1 鉴权

除 `GET /health` 外，所有 API 请求均需携带：

```http
Authorization: Bearer <API_KEY>
```

有请求体的 HTTP 请求使用：

```http
Content-Type: application/json
```

API Key 用于访问本地自动化服务，不会替代美客浏览器账号登录或提升账号权限。

### 2.2 请求和响应

- 请求体为 JSON 对象，不能使用数组、字符串或 `null` 作为顶层值。
- 请求体上限为 256 KiB；超限时连接可能直接关闭。
- 本文接口成功时均返回 `200`，包括创建和删除操作。
- 响应为 JSON，并带有 `Cache-Control: no-store`。
- 店铺、节点、成员及凭据的路径 ID 为 UUID。格式不合法的路径 ID 返回 `404`。
- 本文路径中的 `:id`、`:passwordId`、`:otpId`、`:passkeyId` 均需替换为真实值。
- 列表接口未提供通用分页参数；活动列表最多返回最近 200 条。
- 店铺、节点和成员的 `PATCH` 保留未传字段；具体清空规则见各章节。不要使用 `null` 代替省略字段。

错误响应：

```json
{
  "error": "错误说明",
  "code": "not_found"
}
```

程序应根据 HTTP 状态码及 `code` 处理错误，不依赖 `error` 文案或语言。

### 2.3 权限

- 创建或删除店铺、访问节点接口以及团队管理均需要所有者或管理员 `admin` 权限；普通成员 `member` 需要店铺授权。
- 店铺 `read` 权限允许启动店铺及读取已保存凭据。
- 店铺 `full` 权限还允许修改店铺及已保存凭据，但不授予团队管理权限。
- 创建普通成员不会自动授予店铺访问权限，应另外设置店铺成员授权。

## 3. 快速开始

以下命令使用 Bash/zsh 语法。将 API Key 和店铺 ID 替换为实际值。

```bash
export MERCA_API_BASE='http://127.0.0.1:43210'
export MERCA_API_KEY='替换为客户端中的 API Key'

curl -sS "$MERCA_API_BASE/health"

curl -sS "$MERCA_API_BASE/v1/engine" \
  -H "Authorization: Bearer $MERCA_API_KEY"

curl -sS "$MERCA_API_BASE/v1/stores" \
  -H "Authorization: Bearer $MERCA_API_KEY"

export STORE_ID='替换为店铺 UUID'

curl -sS -X POST "$MERCA_API_BASE/v1/stores/$STORE_ID/launch" \
  -H "Authorization: Bearer $MERCA_API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"startupUrl":"https://example.com","extraArgs":["--remote-debugging-port=9222"]}'
```

启动成功返回：

```json
{"ok": true}
```

此时只表示浏览器进程启动成功，不表示网页、登录或 CDP 已就绪。等待调试端口可用后，可查询：

```bash
curl -sS http://127.0.0.1:9222/json/version
```

使用返回的 `webSocketDebuggerUrl`，或使用自动化工具支持的 CDP HTTP 地址，连接该浏览器进行页面操作。`43210` 是美客浏览器 API 端口，`9222` 是示例浏览器调试端口，两者不可混用。并行运行多个店铺时，应为每个浏览器分配不同的调试端口。

使用成员账号进行 CDP 页面自动化前，请关闭该成员的「禁用开发者工具」选项，并重新启动店铺。

关闭本次由 API 启动的店铺：

```bash
curl -sS -X POST "$MERCA_API_BASE/v1/stores/$STORE_ID/close" \
  -H "Authorization: Bearer $MERCA_API_KEY"
```

## 4. 服务、账号与平台

| 方法 | 路径 | 说明 | 成功响应 |
| --- | --- | --- | --- |
| GET | `/health` | 服务存活检查，无需鉴权 | `{ "ok": true }` |
| GET | `/v1/engine` | 浏览器内核状态 | `{ "ready": true, "version": "..." }` |
| GET | `/v1/session` | 客户端登录会话状态 | `{ "session": "active" }` |
| GET | `/v1/account/entitlements` | 当前账号权益 | `{ "entitlements": { ... } }` |
| GET | `/v1/platforms` | 平台及站点目录 | `{ "platforms": [ ... ] }` |
| GET | `/v1/activity` | 当前账号最近活动 | `{ "entries": [ ... ] }` |

### 4.1 内核与会话状态

`GET /v1/engine` 的 `version` 是浏览器内核版本，不是美客浏览器客户端版本；无法取得版本时为空字符串。`ready` 不代表任何店铺已经启动，也不代表调试端口可用。

`GET /v1/session` 的 `session` 可为：

| 值 | 含义 |
| --- | --- |
| `active` | 会话有效 |
| `revoked` | 会话已撤销 |
| `unknown` | 无法确定状态，例如请求失败或超时 |

`/health` 返回正常不代表账号已登录、拥有权限或内核已就绪。

### 4.2 账号权益

`entitlements` 包含：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `tier` | string | 套餐标识 |
| `maxStores` | number | 店铺数量上限 |
| `usedStores` | number | 已使用店铺数量 |
| `renewsAt` | string 或 null | 续期时间 |
| `cancelAt` | string 或 null | 取消时间 |
| `status` | string 或 null | 订阅状态 |

### 4.3 平台目录

可通过 `platformId` 查询参数筛选单个平台：

```http
GET /v1/platforms?platformId=<PLATFORM_ID>
```

响应结构：

```json
{
  "platforms": [
    {
      "id": "平台 ID",
      "name": "平台名称",
      "sites": [
        {"id": "站点 ID", "name": "站点名称"}
      ]
    }
  ]
}
```

名称跟随客户端当前语言。创建店铺时使用返回的 `id`，不要使用显示名称。筛选不存在的平台返回 `404`。

### 4.4 活动记录

`GET /v1/activity` 和 `GET /v1/members/:id/activity` 返回相同结构，按时间倒序，最多 200 条：

```json
{
  "entries": [
    {
      "id": "活动 UUID",
      "action": "活动类型",
      "details": {},
      "createdAt": "2026-09-17T08:00:00.000Z"
    }
  ]
}
```

`details` 为活动相关字段，值为字符串或布尔值；具体字段依活动类型而定。

## 5. 店铺管理

| 方法 | 路径 | 说明 | 成功响应 |
| --- | --- | --- | --- |
| GET | `/v1/stores` | 列出当前账号可见店铺 | `{ "stores": [...] }` |
| POST | `/v1/stores` | 创建店铺 | `{ "store": {...} }` |
| GET | `/v1/stores/:id` | 获取店铺详情 | `{ "store": {...} }` |
| PATCH | `/v1/stores/:id` | 更新店铺 | `{ "store": {...} }` |
| DELETE | `/v1/stores/:id` | 删除店铺及本机对应数据、缓存 | `{ "ok": true }` |

### 5.1 创建与更新字段

| 字段 | 类型 | 创建必填 | 说明 |
| --- | --- | --- | --- |
| `name` | string | 是 | 店铺名称，去除首尾空白后非空，最长 120 字符 |
| `platformId` | string | 是 | 平台 ID；创建后不可更改 |
| `siteIds` | string[] | 是 | 至少一个站点 ID；更新时整体替换该数组 |
| `username` | string | 否 | 店铺登录账号，最长 320 字符，默认空字符串；非空值在同一团队、同一平台的未删除店铺中不可重复 |
| `password` | string | 否 | 店铺登录密码，最长 512 字符，默认空字符串 |
| `nodeId` | string | 否 | 节点 UUID；空字符串表示不绑定或解除绑定 |
| `fingerprint` | object | 否 | 指纹配置，见下表 |
| `appearanceLabel` | string | 否 | 外观标签，最长 120 字符，默认空字符串 |
| `appearanceBackgroundColor` | string | 否 | 外观背景色，默认空字符串 |
| `appearanceForegroundColor` | string | 否 | 外观前景色，默认空字符串 |

可以先创建未绑定节点的店铺，但启动和生成启动命令前必须绑定节点。

`fingerprint` 字段：

| 字段 | 类型 | 创建默认值 | 说明 |
| --- | --- | --- | --- |
| `browserVersion` | string | `""` | 报告版本，最长 32 字符；留空或填 `auto` 时自动选择与内核同大版本的最高可用版本 |
| `hardwareConcurrency` | number | 随机 | CPU 线程数，整数 `1`–`256` |
| `cpuPerformance` | number | 随随机组合 | CPU 性能档位，整数 `0`–`4` |
| `deviceMemory` | number | 随机 | 报告内存，可选 `2`、`4`、`8`、`16` 或 `32` GB，不分配真实内存 |
| `speechVoicesMode` | string | `override` | `none` 不覆盖，`override` 完全覆盖，`append_google` 仅追加 Google 语音 |
| `fontAllowlistOverride` | boolean | `true` | 按当前系统应用预设字体白名单 |
| `disableCanvasNoise` | boolean | `false` | 禁用 Canvas 2D 像素读回和图片导出噪声；默认保持开启，不影响 WebGL、WebGPU 或其他噪声，修改后需重启浏览器 |
| `webrtcIpPolicy` | string | `disable_non_proxied_udp` | WebRTC IP 策略 |

每次创建都会随机选择 CPU 线程数 `8/12/16/20/24/32` 和内存 `16/32 GB`；随机组合中，8 线程的性能档位为 `3`，其余为 `4`。显式字段覆盖对应预设，更新时省略则保留原值，启动时不会重新随机。

`cpuPerformance` 档位：`0 = Unknown`、`1 = Low`、`2 = Mid`、`3 = High`、`4 = Ultra`。

`webrtcIpPolicy` 支持：

- `disable_non_proxied_udp`
- `default_public_interface_only`
- `default_public_and_private_interfaces`
- `default`

允许 UDP 的模式可能暴露直连 IP，而非节点 IP。修改后需关闭并重新启动浏览器。

创建店铺时，未提供的指纹字段使用上述默认值。更新 `fingerprint` 时，只覆盖传入的子字段。

创建请求示例，平台、站点和节点 ID 请替换为接口查询到的实际值：

```json
{
  "name": "运营店铺 A",
  "platformId": "<PLATFORM_ID>",
  "siteIds": ["<SITE_ID>"],
  "username": "seller@example.com",
  "password": "<STORE_PASSWORD>",
  "nodeId": "<NODE_UUID>",
  "fingerprint": {
    "hardwareConcurrency": 8,
    "deviceMemory": 8,
    "webrtcIpPolicy": "disable_non_proxied_udp"
  }
}
```

局部更新示例：

```http
PATCH /v1/stores/:id
```

```json
{
  "name": "运营店铺 B",
  "fingerprint": {"hardwareConcurrency": 4}
}
```

### 5.2 店铺响应对象

店铺列表中的每项包含：

- `id`、`name`、`platformId`、`siteIds`、`username`
- `nodeId`、`nodeBound`
- `fingerprint`
- `appearanceLabel`、`appearanceBackgroundColor`、`appearanceForegroundColor`
- `createdAt`、`updatedAt`、可选的 `lastLaunchedAt`

`nodeBound` 表示是否已设置 `nodeId`，不代表节点在线或浏览器正在运行。

店铺详情、创建和更新响应在上述字段基础上增加：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `password` | string | 店铺登录密码 |
| `node` | object 或 null | 绑定节点摘要，未绑定时为 null |

`node` 摘要包含 `name`、`ip`、`country`、`timezone`、`acceptLanguages`，不包含代理密码。代理连接详情通过节点接口获取。

店铺登录字段与第 9 节的已保存网站密码集合是不同数据。

## 6. 浏览器启动与关闭

| 方法 | 路径 | 说明 | 成功响应 |
| --- | --- | --- | --- |
| POST | `/v1/stores/:id/launch` | 启动店铺浏览器 | `{ "ok": true }` |
| POST | `/v1/stores/:id/command` | 获取启动命令，不启动浏览器 | `{ "executable": "...", "args": [...] }` |
| POST | `/v1/stores/:id/close` | 关闭通过 `launch` 启动的浏览器 | `{ "ok": true }` |

### 6.1 启动参数

`launch` 和 `command` 接受相同请求体；不需要自定义参数时发送 `{}`：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `startupUrl` | string | 否 | 启动时打开的地址；空字符串按未设置处理 |
| `extraArgs` | string[] | 否 | 额外 Chromium 命令行参数，默认空数组 |

```json
{
  "startupUrl": "https://example.com",
  "extraArgs": ["--remote-debugging-port=9222"]
}
```

启动会继承客户端全局启动参数，再追加本次 `extraArgs`；传入空数组不会清除全局参数。`extraArgs` 不能覆盖美客浏览器管理的店铺数据目录和浏览器生命周期参数。

`launch` 和 `command` 均受[登录保护](https://merca.getchrovia.com/zh/docs/login-protection)限制。店铺登录账号与节点账号记录冲突时，返回 `400 node_account_conflict`，请先在客户端查看和处理账号记录。

`launch` 成功仅代表浏览器已启动。重复调用可能关闭并重新启动该店铺，请勿用它轮询运行状态。

### 6.2 获取启动命令

```http
POST /v1/stores/:id/command
```

响应字段：

| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `executable` | string | 浏览器可执行文件路径 |
| `args` | string[] | 完整启动参数 |

请将 `executable` 和 `args` 原样交给进程启动 API，不要拼接成 shell 命令，也不要自行覆盖店铺数据目录。

`command` 不会启动浏览器，但会更新店铺的最后启动时间和活动记录。店铺登录账号非空时，还可能新增节点账号记录，影响其他账号能否在该 IP 上使用同一平台；即使不执行返回的命令，也会产生这些影响。

如使用 `--remote-debugging-pipe`，应选择 `command`，由调用方创建进程并正确连接调试管道；`launch` 不会把管道返回给调用方。

### 6.3 生命周期

- 使用浏览器期间，请保持美客浏览器桌面客户端运行。
- `close` 仅管理通过 `launch` 启动的浏览器，不管理客户端界面或调用方依据 `command` 自行启动的进程。
- 没有可关闭的 API 启动记录时，`close` 返回 `404`，错误码为 `not_running`。浏览器已自行退出时，首次 `close` 仍可能成功，因此不能用此接口判断运行状态。
- `close` 成功表示已发出关闭请求，不保证浏览器进程已退出。
- 店铺使用独立本地数据目录。不要对同一店铺并发启动多个实例。

## 7. 网络节点管理

| 方法 | 路径 | 说明 | 成功响应 |
| --- | --- | --- | --- |
| GET | `/v1/nodes` | 节点列表，不返回代理密码 | `{ "nodes": [...] }` |
| POST | `/v1/nodes` | 创建节点 | `{ "node": {...} }` |
| GET | `/v1/nodes/:id` | 节点详情，包含代理密码 | `{ "node": {...} }` |
| PATCH | `/v1/nodes/:id` | 更新节点 | `{ "node": {...} }` |
| DELETE | `/v1/nodes/:id` | 删除节点 | `{ "ok": true }` |

删除节点前，必须先解除所有店铺与该节点的绑定，否则返回 `400 node_in_use`。

### 7.1 创建与更新字段

| 字段 | 类型 | 创建必填 | 默认值或说明 |
| --- | --- | --- | --- |
| `name` | string | 否 | 默认空字符串，最长 120 字符 |
| `protocol` | string | 否 | `http`、`https`、`socks5`，默认 `http` |
| `host` | string | 是 | 代理主机名或 IP，最长 255 字符 |
| `port` | number | 否 | 整数 `1`–`65535`，默认 `8080` |
| `username` | string | 否 | 默认空字符串，最长 512 字符 |
| `password` | string | 否 | 默认空字符串，最长 512 字符 |
| `acceptProxyChain` | boolean | 否 | 是否接受客户端已配置的代理链，默认 `true` |
| `ip` | string | 否 | 已知出口 IP，必须与 `country` 一起提供 |
| `country` | string | 否 | 两字母国家或地区代码，例如 `us` |
| `timezone` | string | 否 | 手动提供检测结果时可设置 IANA 时区，例如 `America/New_York` |
| `region`、`city`、`asn`、`org` | string | 否 | 手动检测结果的附加信息 |
| `latitude`、`longitude` | number | 否 | 手动检测结果的坐标 |

```json
{
  "name": "美国节点 A",
  "protocol": "socks5",
  "host": "proxy.example.com",
  "port": 1080,
  "username": "<PROXY_USERNAME>",
  "password": "<PROXY_PASSWORD>",
  "acceptProxyChain": true
}
```

使用限制：

- 同一出口 IP 不能重复添加，即使使用不同账号添加也会返回 `node_already_added`。
- `ip` 和 `country` 必须同时提供或同时省略；仅提供其中一个会返回 `node_probe_required`。
- 手动提供时区等附加地理字段时，必须同时提供 `ip` 和 `country`，无需修改连接信息。非空时区必须是有效的 IANA 时区；留空时跟随当前电脑。
- 更新连接信息或检测结果时，出口 IP 必须与原节点保持一致，否则返回 `node_ip_changed`。需要更换出口 IP 时应创建新节点。

### 7.2 节点响应

节点详情包含：

`id`、`name`、`protocol`、`host`、`port`、`username`、`password`、`acceptProxyChain`、`ip`、`country`、`timezone`、`acceptLanguages`。

列表省略 `password`；创建、更新和详情响应包含该字段。请求支持的附加地理字段不代表响应也返回这些字段。

## 8. 团队成员与店铺授权

### 8.1 成员接口

| 方法 | 路径 | 说明 | 成功响应 |
| --- | --- | --- | --- |
| GET | `/v1/members` | 成员列表 | `{ "members": [...] }` |
| POST | `/v1/members` | 创建成员 | `{ "member": {...}, "downloadUrl": "..." }` |
| GET | `/v1/members/:id` | 成员详情 | `{ "member": {...} }` |
| PATCH | `/v1/members/:id` | 更新成员 | `{ "member": {...}, "downloadUrl": "..." }` |
| DELETE | `/v1/members/:id` | 删除成员 | `{ "ok": true }` |
| PUT | `/v1/members/:id/disabled` | 设置禁用状态 | `{ "member": {...} }` |
| GET | `/v1/members/:id/activity` | 查询成员活动 | `{ "entries": [...] }` |

成员接口不包含团队所有者。成员路径参数使用成员对象的 `id`，不是 `userId`。

创建和更新字段：

| 字段 | 类型 | 创建必填 | 说明 |
| --- | --- | --- | --- |
| `nickname` | string | 是 | 昵称，1–32 字符 |
| `username` | string | 是 | 团队登录账号，2–32 位英文字母、数字或下划线；去除首尾空白并转为小写，团队内不可重复 |
| `password` | string | 是 | 团队登录密码，8–128 字符；更新时不传或传空字符串均保留原密码 |
| `role` | string | 否 | `admin` 或 `member`，默认 `member` |
| `disableDevtools` | boolean | 否 | 禁用开发者工具，默认 `true` |
| `passwordRevealBlock` | boolean | 否 | 限制密码显示，默认 `true` |

```json
{
  "nickname": "运营一组",
  "username": "operator_01",
  "password": "<MEMBER_PASSWORD>",
  "role": "member",
  "disableDevtools": true,
  "passwordRevealBlock": true
}
```

成员对象包含 `id`、`userId`、`username`、`nickname`、`role`、`disableDevtools`、`passwordRevealBlock`、`disabled`、`createdAt`，不返回登录密码。

禁用成员的请求体：

```json
{"disabled": true}
```

恢复时传 `false`。禁用成员会使其已有登录会话失效。不能修改自己的角色、删除自己，或对自己调用禁用状态接口。

### 8.2 店铺成员授权

| 方法 | 路径 | 说明 | 成功响应 |
| --- | --- | --- | --- |
| GET | `/v1/stores/:id/members` | 查询可授权成员及当前权限 | `{ "members": [...] }` |
| PUT | `/v1/stores/:id/members` | 整体替换授权集合 | `{ "members": [...] }` |

需要所有者或管理员权限及付费套餐。列表只包含可授权的 `member` 账号；所有者及管理员已有管理权限，不在此列表中。

响应中的每项包含 `userId`、`username`、`nickname`、`access`。`access` 可为 `read`、`full` 或表示未授权的 `null`。

替换请求：

```json
{
  "grants": [
    {"userId": "<MEMBER_USER_UUID>", "access": "read"},
    {"userId": "<ANOTHER_MEMBER_USER_UUID>", "access": "full"}
  ]
}
```

这里使用 `userId`，不是成员接口路径使用的 `id`。同一个 `userId` 不可重复，且必须来自可授权成员列表。

这是整体替换，不是追加：未包含的原有成员授权将被移除。传入 `{ "grants": [] }` 清空普通成员的店铺授权。

## 9. 已保存的网站凭据

本节读取操作需要店铺 `read` 权限，写入和删除需要 `full` 权限。

### 9.1 网站密码

| 方法 | 路径 | 说明 | 成功响应 |
| --- | --- | --- | --- |
| GET | `/v1/stores/:id/passwords` | 密码条目摘要，不含密码 | `{ "credentials": [...] }` |
| POST | `/v1/stores/:id/passwords` | 保存网站密码 | `{ "ok": true }` |
| GET | `/v1/stores/:id/passwords/:passwordId` | 获取单条凭据，包含密码 | `{ "credential": {...} }` |
| PATCH | `/v1/stores/:id/passwords/:passwordId` | 修改未锁定凭据 | `{ "ok": true }` |
| DELETE | `/v1/stores/:id/passwords/:passwordId` | 删除未锁定凭据 | `{ "ok": true }` |
| PUT | `/v1/stores/:id/passwords/:passwordId/locked` | 锁定或解锁凭据 | `{ "ok": true }` |

保存请求：

```json
{
  "origin": "https://example.com",
  "username": "seller@example.com",
  "password": "<WEBSITE_PASSWORD>"
}
```

| 字段 | 规则 |
| --- | --- |
| `origin` | 必填，HTTP/HTTPS URL，最长 2048 字符 |
| `username` | 必填，非空，最长 512 字符 |
| `password` | 必填，非空，最长 2048 字符 |

`PATCH` 只接受上述三个字段中的一个或多个，至少提供一个；不允许空字符串。`signon_realm` 自动生成，不接受作为请求字段。

密码摘要字段：`id`、`origin`、`signon_realm`、`username`、`locked`。单条详情另含 `password`。

同一网站登录域与用户名的未锁定条目会在保存时更新，锁定条目拒绝覆盖。保存接口不返回条目 ID，需要再次查询列表。

锁定请求：

```json
{"locked": true}
```

传 `false` 解锁。锁定阻止修改、删除及浏览器同步覆盖，不阻止读取或使用密码。

修改会在后续启动时提供给浏览器；删除不会修改网站账号，也不会移除运行中浏览器已加载的凭据。后续浏览器同步可能覆盖未锁定条目或重新创建已删除条目。

### 9.2 TOTP 动态验证码

| 方法 | 路径 | 说明 | 成功响应 |
| --- | --- | --- | --- |
| GET | `/v1/stores/:id/otp` | 查询条目元数据 | `{ "entries": [...] }` |
| POST | `/v1/stores/:id/otp` | 保存或更新 TOTP | `{ "entry": {...} }` |
| DELETE | `/v1/stores/:id/otp/:otpId` | 移除条目 | `{ "ok": true }` |
| GET | `/v1/stores/:id/otp/:otpId/code` | 获取当前验证码 | `{ "id": "...", "code": "...", "expiresAt": "..." }` |

保存请求字段：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `secret` | string | 是 | Base32 密钥，去除空白并转大写后最长 256 字符；不是 `otpauth://` URL |
| `label` | string | 否 | 标签，最长 120 字符；省略时根据发行方和账号生成，均为空时使用 `Authenticator` |
| `issuer` | string | 否 | 发行方，最长 120 字符，默认空字符串 |
| `accountName` | string | 否 | 账号，最长 180 字符，默认空字符串 |
| `algorithm` | string | 否 | `SHA1`、`SHA256`、`SHA512`，默认 `SHA1` |
| `digits` | number | 否 | `6` 或 `8`，默认 `6` |
| `period` | number | 否 | 有效周期秒数，整数 `15`–`120`，默认 `30` |

列表和保存响应中的元数据包含 `id`、`label`、`issuer`、`accountName`、`algorithm`、`digits`、`period`，不返回密钥。

同一店铺下相同的标准化密钥会更新或恢复已有条目；不同密钥创建新条目。再次保存时，省略的元数据与设置采用默认值，不保留旧值。

验证码响应示例：

```json
{
  "id": "<OTP_UUID>",
  "code": "012345",
  "expiresAt": "2026-09-17T08:00:30.000Z"
}
```

`code` 为字符串，应保留前导零。验证码按运行美客浏览器的电脑时钟生成；`expiresAt` 为 UTC ISO 时间。此接口不会自动提交验证码。保存或删除条目不会开启或关闭网站上的两步验证。

### 9.3 Passkey

| 方法 | 路径 | 说明 | 成功响应 |
| --- | --- | --- | --- |
| GET | `/v1/stores/:id/passkeys` | 查询 Passkey 元数据 | `{ "entries": [...] }` |
| DELETE | `/v1/stores/:id/passkeys/:passkeyId` | 移除已保存条目 | `{ "ok": true }` |

每项包含 `id`、`rpId`、`userName`、`userDisplayName`，不返回私钥。

自动化 API 不提供创建或导入 Passkey 的端点。删除只影响美客浏览器中保存的条目及后续启动，不会在网站注销凭据，也不会移除运行中浏览器已加载的副本。

## 10. 常见错误

| HTTP 状态 | `code` | 含义 |
| --- | --- | --- |
| 400 | `body_invalid` | 请求体不是合法 JSON |
| 400 | `body_not_object` | 请求体顶层不是对象 |
| 400 | `input_invalid` | 指纹、授权、密码或 TOTP 字段无效 |
| 400 | `profile_name_required` | 缺少店铺名称 |
| 400 | `platform_id_required` | 缺少平台 ID |
| 400 | `platform_id_immutable` | 尝试更改店铺所属平台 |
| 400 | `site_ids_required` / `site_ids_invalid` | 站点集合缺失或格式错误 |
| 400 | `node_timezone_invalid` | 非空节点时区无效 |
| 400 | `startup_url_invalid` | 启动地址类型错误 |
| 400 | `extra_args_invalid` | 启动参数不是字符串数组，或包含禁止覆盖的参数 |
| 400 | `node_host_required` | 缺少节点主机地址 |
| 400 | `node_port_invalid` | 节点端口不合法 |
| 400 | `node_protocol_invalid` | 不支持的代理协议 |
| 400 | `node_probe_required` | 缺少完整节点检测信息 |
| 400 | `node_probe_failed` | 节点检测失败 |
| 400 | `node_ip_changed` | 更新后的节点出口 IP 与原值不同 |
| 400 | `node_account_conflict` | 店铺登录账号与节点账号记录冲突 |
| 400 | `node_in_use` | 节点仍被店铺绑定，无法删除 |
| 400 | `node_already_added` | 该出口 IP 已被添加 |
| 400 | `organization_username_taken` | 团队内已有成员使用该登录账号 |
| 400 | `store_username_taken` | 同一团队、同一平台已有店铺使用该登录账号 |
| 400 | `account_locked` | 网站密码已锁定，无法覆盖、修改或删除 |
| 400 | `store_members_invalid` | 授权包含不允许的成员或重复成员 |
| 400 | `organization_member_disabled_invalid` | `disabled` 不是布尔值 |
| 400 | `otp_code_failed` | TOTP 验证码生成失败 |
| 401 | `unauthorized` | API Key 缺失或不正确 |
| 401 | `not_authenticated` | 客户端账号未登录或登录失效 |
| 403 | `paid_plan_required` | 需要付费套餐 |
| 403 | `forbidden` | 当前账号无操作权限 |
| 403 | `store_quota_exceeded` | 店铺数量达到套餐上限 |
| 404 | `not_found` | 资源或路由不存在，或路径 ID 格式不合法 |
| 404 | `not_running` | 没有可关闭的 API 启动记录 |
| 405 | `method_not_allowed` | 接口不支持该 HTTP 方法 |
| 409 | `profile_changed` | 启动时店铺信息已变化，请刷新后重试 |
| 413 | `body_too_large` | 请求体超过限制；连接可能直接关闭 |
| 500 | `automation_api_error` | 请求处理失败 |
| 503 | `merca_console_session_not_ready` | 桌面客户端会话尚未准备好 |
| 503 | `browser_engine_not_ready` | 浏览器内核尚未就绪 |

## 相关文档

- [添加网络节点](https://merca.getchrovia.com/zh/docs/import-node)
- [如何绑定店铺](https://merca.getchrovia.com/zh/docs/add-store)
- [如何添加成员](https://merca.getchrovia.com/zh/docs/add-member)
- [店铺指纹如何工作](https://merca.getchrovia.com/zh/docs/store-fingerprint)
