# 开发者 API

用一把 API Key 读取自己账号的状态、套餐、用量和设备，适合接进你自己的脚本或面板。
节点清单与取配置需要单独授权，不在这份公开文档里。

Base URL：`https://clash.cupzz.cn/api/v1`

## 认证

每个请求带上 `Authorization` 头，值是 `Bearer` 加一个空格，再加你的 API Key：

```bash
curl https://clash.cupzz.cn/api/v1/ext/account \
  -H "Authorization: Bearer cncp_你的密钥"
```

**API Key 从哪来**
由站点管理员发放：可以单独发给你，也可能随你购买的套餐附带。
它只在生成的那一刻显示一次，之后谁也查不回来（服务端只存哈希）。
如果丢了，请管理员重新生成一把。

**还需要「允许 API 使用」的套餐**
光有 Key 还不够：你的账号名下必须至少有一档允许 API 使用的生效套餐。
站点可以在套餐模板上开关这一项，而注册赠送的免费套餐默认不允许。
没有这样的套餐时，所有接口都返回 `403` 与错误码 `api_plan_required`（这种调用不会消耗配额）。

**站点可能整体关掉了 API**
站长可以一键关闭对外 API。这时所有 `/api/v1/ext/*` 调用返回 `403` 与 `api_disabled`，与你的 Key 是否有效无关。
遇到这个错误码请联系站长，换一把 Key 没有用。

**保管好它**
API Key 等同于你的账号访问权。不要写进前端代码、不要提交到公开仓库、不要贴进聊天群。
怀疑泄露时请管理员立即停用。

## 配额

每次调用消耗一次配额（包括查询配额本身）。配额有两种模式，由管理员设置：

| 模式 | 含义 | 什么时候恢复 |
| --- | --- | --- |
| `total` | 总共 N 次，用完为止 | 不恢复（需要管理员调整） |
| `period` | 每个周期 N 次 | 到下一个周期自动重置（按天、周、月、季、年） |

剩余次数、下次重置时间随时可以在用量接口里查到。
配额用尽时接口返回 `429` 与错误码 `api_quota_exceeded`。

## 接口

下面这些都需要 API Key。响应统一是 `{"ok":true,"data":…}` 的信封。

节点清单与取配置这两个接口会返回真实节点信息，只对单独授权过的 Key 开放，因此不写进公开文档。
需要对接时联系站长单独开通。

### 账号概览

`GET /api/v1/ext/account`

账号概览：状态、设备数、以及名下所有套餐（含到期、用量、能否连接）。

```json
{
  "ok": true,
  "data": {
    "accountId": "you@example.com",
    "email": "you@example.com",
    "qq": "12345678",
    "status": "active",
    "createdAt": 1792064568,
    "deviceLimit": 2,
    "devices": 1,
    "plans": [
      {
        "grantId": "g_xxxxxxxxxx",
        "title": "标准套餐",
        "planName": "standard",
        "serviceStatus": "normal",
        "expiresAt": 1794656567,
        "remainingDays": 30,
        "trafficLimit": 107374182400,
        "usedBytes": 21474836480,
        "remainingBytes": 85899345920,
        "deviceLimit": 2,
        "canConnect": true,
        "denyReason": ""
      }
    ],
    "traffic": { "unlimited": false, "limitBytes": 107374182400, "usedBytes": 21474836480, "remainingBytes": 85899345920 },
    "expiry": { "expiresAt": 1794656567, "remainingDays": 30 },
    "invite": {
      "enabled": true,
      "code": "A1B2C3D4",
      "link": "https://clash.cupzz.cn/register?invite=A1B2C3D4",
      "invitedCount": 3,
      "notice": "",
      "inviterReward": "每邀请 1 人：+10 GB 流量",
      "inviteeReward": "被邀请注册：+5 GB 流量"
    }
  }
}
```

`canConnect` 为 false 时 `denyReason` 会说明原因（到期、流量用尽、被停用等）。流量单位是字节。
`traffic` 与 `expiry` 是整个账号的汇总：多条套餐相加；有任意一条不限流量时 `unlimited` 为 true，两个额度字段为 `-1`。
`expiry` 取最近到期的那条，那才是「什么时候会断」。

### 查询指定账号：发验证码

`POST /api/v1/ext/query/code`

给目标账号的邮箱发一个查询验证码。邮箱由服务端配置的发件邮箱发出，你无法指定发件人。
响应永远是同一句话，不暴露该邮箱是否在本站注册过。

```bash
curl -X POST https://clash.cupzz.cn/api/v1/ext/query/code \
  -H "Authorization: Bearer cncp_你的密钥" \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com"}'
```

```json
{"ok":true,"data":{"sent":true,"message":"如果该邮箱已注册，验证码已发出。","serverTime":1792064613}}
```

站点没有开启邮箱验证码时返回 `403`：这条路径不会被悄悄放行，因为那等于开了一个不需要凭据的账号查询入口。

### 查询指定账号

`POST /api/v1/ext/query`

查询指定账号（不只是你自己的）。必须出示目标账号的密码或上面那个验证码，两者给其一即可。
返回的内容与账号概览接口完全一致。

```bash
curl -X POST https://clash.cupzz.cn/api/v1/ext/query \
  -H "Authorization: Bearer cncp_你的密钥" \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com","password":"该账号的密码"}'

# 或者用验证码：
curl -X POST https://clash.cupzz.cn/api/v1/ext/query \
  -H "Authorization: Bearer cncp_你的密钥" \
  -H "Content-Type: application/json" \
  -d '{"email":"user@example.com","code":"123456"}'
```

凭据不对与「该邮箱没有账号」返回完全相同的响应（`401 unauthorized`）。
失败次数与网页登录共用同一套限速，换个入口多试几次是不行的。
验证码一次性、有有效期，且只发给已注册的邮箱。

### 设备列表

`GET /api/v1/ext/devices`

自己账号下的设备列表。

```json
{
  "ok": true,
  "data": {
    "devices": [
      {
        "deviceId": "d_1a2b3c…",
        "name": "我的手机",
        "platform": "android",
        "status": "active",
        "createdAt": 1792064568,
        "lastSeenAt": 1792064600
      }
    ]
  }
}
```

### 注册设备

`POST /api/v1/ext/devices/register`

登记一台设备。登记后会自动绑定到你的账号并激活（受设备数上限约束，与客户端登录时的规则一致）。

```bash
curl -X POST https://clash.cupzz.cn/api/v1/ext/devices/register \
  -H "Authorization: Bearer cncp_你的密钥" \
  -H "Content-Type: application/json" \
  -d '{
    "deviceId": "d_你的设备标识",
    "publicKey": "base64 编码的 X25519 公钥",
    "keyId": "k1_公钥指纹",
    "name": "我的脚本"
  }'
```

```json
{"ok":true,"data":{"deviceId":"d_…","status":"active","created":true,"serverTime":1792064568}}
```

`deviceId` 与 `publicKey` 必填；`keyId`、`name` 可选。公钥用于加密下发给该设备的配置。

### 移除设备

`POST /api/v1/ext/devices/remove`

移除自己账号下的一台设备，该设备的登录状态会立刻失效。

```bash
curl -X POST https://clash.cupzz.cn/api/v1/ext/devices/remove \
  -H "Authorization: Bearer cncp_你的密钥" \
  -H "Content-Type: application/json" \
  -d '{"deviceId":"d_要移除的设备"}'
```

```json
{"ok":true,"data":{"removed":true}}
```

### 用量

`GET /api/v1/ext/usage`

这把 Key 的配额与最近的调用统计（按天聚合）。

```json
{
  "ok": true,
  "data": {
    "keyPrefix": "cncp_a1b2c3",
    "label": "给对账脚本用",
    "quotaMode": "period",
    "quotaPeriod": "month",
    "quotaLimit": 10000,
    "used": 42,
    "remaining": 9958,
    "resetAt": 1794742968,
    "expiresAt": 0,
    "lastUsedAt": 1792064613,
    "calls": [
      { "day": "2026-10-15", "endpoint": "/api/v1/ext/account", "calls": 12 }
    ],
    "serverTime": 1792064613
  }
}
```

`remaining` 为 `-1` 表示不限次数；`resetAt` 为 `0` 表示没有「重置」这回事（total 模式）。

### 公开接口（不需要 Key）

`GET /api/v1/stats`

站点实时统计，客户端底部那条数据用的就是它。

```json
{"ok":true,"data":{"onlineUsers":12,"todayConnections":378,"totalConnections":9723}}
```

`GET /api/v1/meta`

站点配置：名称、公告、下载地址、联系方式、版本要求等。

## 错误

失败时返回非 2xx 状态码，响应体是同一个信封形状：

```json
{"ok":false,"error":{"code":"unauthorized","message":"登录状态无效或已过期。"}}
```

| HTTP | code | 含义与下一步 |
| --- | --- | --- |
| 401 | `unauthorized` | Key 无效、被停用或已过期。找管理员确认。 |
| 429 | `api_quota_exceeded` | 配额用尽。等下个周期自动恢复，或让管理员提高额度。 |
| 403 | `forbidden` | 这个 Key 不能做这件事（例如用设备令牌调 Key 专属接口）。 |
| 403 | `account_disabled` | 账号被停用，Key 一并失效。 |
| 403 | `api_disabled` | 站长把整站的对外 API 关掉了。这跟你的 Key 无关，换 Key 也没用，需要联系站长。 |
| 403 | `api_plan_required` | 名下没有「允许 API 使用」的生效套餐（注册赠送的免费套餐默认不允许）。这种调用不消耗配额；需要开通支持 API 的套餐。 |
| 400 | `invalid_request` | 请求参数不对，看 `message` 里的说明。 |
| 404 | `not_found` | 目标不存在（例如要移除的设备不在你的账号下）。 |
