# 云链智码 · HTTP 接口文档

> 版本 **1.1.0** · 生成时间 2026-11-10 22:24:38
> **本站未开启登录限制：生成 / 解析可匿名调用，无需 API Key。**
>
> 基础地址 `https://khm.lxwowbp.cn`
> OpenAPI 3：`https://khm.lxwowbp.cn/api/openapi.json` · Postman Collection：`https://khm.lxwowbp.cn/api/postman.json`

---

## 一、接口一览

| 方法 | 路径 | 说明 |
|---|---|---|
| POST | `/api/encode` | 输入内容，返回太阳码图片（png / jpg / svg / gif）或 JSON |
| GET | `/api/encode` | 同上，参数走 query，可直接放进 `<img src>` |
| POST | `/api/encode/batch` | **批量**生成，一次最多 50 条，逐条返回结果 |
| POST | `/api/decode` | 上传太阳码图片，解析出内容 |
| POST | `/api/decode?family=auto` | 一次上传，自动尝试太阳码与普通二维码（`prefer=qr` 可优先 QR） |
| POST | `/api/qr` | 上传普通二维码（如微信「加好友」名片），取出其中的链接 |

> 公开接口仅包含生成与解析。账户、管理及其他站内接口不在本文档开放。

---

## 二、通用约定

| 项 | 说明 |
|---|---|
| 鉴权 | 生成 / 解析默认匿名可用；也可带 `X-API-Key` 调用，便于计费和限流 |
| 成功 | 图片接口返回二进制，JSON 接口返回对象 |
| 失败 | 统一 `{"error":"原因"}`，配合 4xx/5xx 状态码 |
| 状态码 | `400` 参数/图片不合法 · `403` 目标地址属内网被拒 · `404` 路径不存在 · `413` 请求体过大 · `429` 请求过于频繁 · `503` 服务繁忙 |
| 请求体 | JSON 16MB · multipart 16MB（logo 上限更大时会自动放宽） |
| 请求 ID | 响应头 `X-Request-ID` 标识本次请求，服务端日志里用同一个 ID；报障时把它一起提供即可定位 |
| 上传大小 | 解析图片 **24MB / 24MB**、logo **8MB / 8MB**（匿名 / 登录，后台可配） |
| 图片尺寸 | 单边 ≤ 8000 px，总像素 ≤ 4194 万；GIF 最多取 120 帧 |
| 内容长度 | `text` ≤ 8192 字节（太阳码本身也只能装几十字节） |
| 限速 | 单 IP 120 次/分钟，超限返回 429 并带 `Retry-After` |
| 并发 | 编码/解析同时最多 8 个，超限返回 503 |
| 跨域 | 默认**不允许**跨域；管理员配置来源白名单后才放行（见下） |

### 跨域（CORS）

本站**未开启**跨域：浏览器里的其它网站无法读取本接口的响应。
需要开启请让管理员在后台「站点设置 → 跨域来源白名单」里配置。

开启后，命中的来源会拿到 `Access-Control-Allow-Origin` 等响应头，
`OPTIONS` 预检直接返回 204；未列出的来源请求照常返回，但没有跨域许可头（浏览器会拦掉）。

### 出站请求限制

`logo`、`badge`、`url` 里的 http(s) 地址由服务端下载，且**只能访问公网**：
回环 / 内网 / 链路本地 / 云元数据（`169.254.169.254`）/ CGNAT / 组播一律拒绝（返回 403），
最多跟随 3 次重定向，每一跳都重新校验。默认也**不允许把参数当本地文件路径**读取，
如需读取服务器本地图片，设置环境变量 `ALLOW_LOCAL_IMAGE=1`。

---

## 三、POST /api/encode

生成太阳码。请求体可以是 JSON，也可以是 multipart（可带 `logo` 文件），或普通表单。

### 请求参数

| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| `text` | string | 是 | 要编码的内容，必填。接口上限 8192 字节；但一枚码只能装几十字节（URL 约 58 字符、中文按字节约 44 字节），超了返回 400 |
| `format` | string | 否 | 输出格式，默认 png。png/jpg/gif 返回图片二进制，svg 返回矢量文本，json 返回结构体 |
| `size` | integer | 否 | 位图边长（像素），120~2000，默认 600；对 svg 无效 |
| `margin` | integer | 否 | 位图四周白边（像素），0~200，默认 0 |
| `fg` | string | 否 | 前景色（码点颜色），#rrggbb，默认 #000000 |
| `bg` | string | 否 | 背景色，#rrggbb，默认 #ffffff |
| `level` | string | 否 | 允许的最低纠错等级，默认 L；容量够时自动取更高等级（响应里的 level 是实际值） |
| `version` | string | 否 | 码格版本：auto（默认，取最小可容纳）/ 0 / 1 / 2（钉死版本，装不下会报错） |
| `mask` | integer | 否 | 掩码：-1 自动在 0~31 里按评分选最优，也可固定 0~31 |
| `logo` | string | 否 | 中心 logo：data URL / http(s) 地址 / 裸 base64 / 空字符串不画；multipart 时也可以传 logo 文件。服务器本地路径需开 ALLOW_LOCAL_IMAGE。GIF 动图 + format=gif 会逐帧保留 |
| `logoRatio` | number | 否 | logo 半径缩放，0.2~3.0，默认 1（半径 = 108/600 边长） |
| `badge` | string | 否 | 角落徽标：default（内嵌图，SVG 写成 data URI、离线可用）/ native（写回远程 URL）/ none（不画）/ 自定义图片地址 |
| `style` | string | 否 | 笔画风格（只决定形状）：classic（默认，等宽胶囊，与微信原生一致）/ hand（手绘：笔线抖动、粗细不匀、徽标也手贴）/ dot（圆点）/ block（方块）/ line（线描：细环+中心点）。装饰性抖动都控制在半格内，不影响扫码 |
| `color` | string | 否 | 配色（只决定墨色，可与任意笔画风格组合）：solid（默认，用 fg）/ rainbow（色环：按半径渐变，内圈青→外圈品红）/ random（彩虹：每一笔随机一色）/ ghost（隐形：墨色只比底色深几级，肉眼几乎看不出，徽标自动隐藏）。除 solid 外都不使用 fg |
| `ghost` | integer | 否 | 隐形配色的深度：墨色比底色深几级（2~40，默认 6）。越小越隐形、越难扫：2~4 基本看不见但只有程序能解，8 以上手机更容易扫出来 |

### 示例

```bash
curl -X POST https://khm.lxwowbp.cn/api/encode \
  -H 'Content-Type: application/json' \
  -d '{"text":"https://example.com/","format":"png","size":600}' \
  -o code.png

# 带中心 logo（本地文件上传）
curl -X POST https://khm.lxwowbp.cn/api/encode \
  -F 'text=https://example.com/' -F 'format=png' -F 'logo=@logo.png'
```

请求体示例（行尾 `//` 是字段说明，粘进代码时删掉即可）：

```jsonc
{
  "text": "https://example.com/",  // 必填：要编码的内容（接口上限 8192 字节）
  "format": "png",                 // 输出格式：png / jpg / svg / gif / json
  "size": 600,                     // 位图边长 120~2000（svg 忽略）
  "margin": 0,                     // 位图四周白边 0~200
  "fg": "#000000",                 // 前景色（码点）#rrggbb
  "bg": "#ffffff",                 // 背景色 #rrggbb
  "level": "L",                    // 允许的最低纠错等级 L/M/Q/H，容量够会自动升级
  "version": "auto",               // auto 取最小可容纳；也可钉死 0 / 1 / 2
  "mask": -1,                      // -1 自动选最优掩码；0~31 固定
  "logo": "",                      // 中心 logo：data URL / http(s) / 裸 base64，空=不画
  "logoRatio": 1,                  // logo 半径缩放 0.2~3，1 表示 108/600
  "badge": "default",              // 角落徽标：default / native / none / 图片地址
  "style": "classic",              // 笔画风格：classic / hand / dot / block / line
  "color": "solid",                // 配色：solid（用 fg）/ rainbow（色环）/ random（彩虹）/ ghost（隐形）
  "ghost": 6                       // 隐形深度：墨色比底色深几级（color=ghost 时生效）
}
```

### 返回

**`format=png|jpg|gif`** → 对应 `Content-Type` 的图片二进制：

| 响应头 | 含义 |
|---|---|
| `X-WXCode-Version` | 实际使用的版本（0~2） |
| `X-WXCode-Level` | 实际纠错等级 `L`/`M`/`Q`/`H` |
| `X-WXCode-Mask` | 选中的掩码 0~31 |
| `X-WXCode-Typebits` | 18 位格式位 |
| `X-WXCode-Mode` | `NUMERIC` / `ALPHANUMERIC` / `BYTE` |
| `X-WXCode-Codewords` / `-DataCodewords` / `-ECCodewords` | 码字总数 / 数据 / 纠错 |
| `Content-Disposition` | `inline; filename="wxcode-v0Q-m14.png"` |

**`format=svg`** → `image/svg+xml`，坐标为 float32 逐步舍入，与原生实现一致；
徽标默认内嵌 data URI，单独拷走也能显示。需要与原生逐字节一致时传 `badge=native`。

**`format=json`** →

| 字段 | 类型 | 说明 |
|---|---|---|
| `text` | string | 实际编码的内容，与请求的 text 一致 |
| `format` | string | 本次输出格式，固定 json |
| `version` | integer | 码格版本 0 / 1 / 2，v0 最小 |
| `level` | string | 实际纠错等级 L / M / Q / H，容量够时会高于请求的 level |
| `levelIndex` | integer | 纠错等级下标：0=L、1=M、2=Q、3=H |
| `mask` | integer | 实际掩码 0~31；请求 mask=-1 时按评分自动选优 |
| `typebits` | integer | 18 位格式信息位（版本 + 纠错等级 + 掩码的编码） |
| `mode` | string | 数据模式：NUMERIC 纯数字 / ALPHANUMERIC 字母数字 / BYTE 字节 |
| `transformed` | string | 仅 ALPHANUMERIC 出现：真正写进码里的变换文本 |
| `codewords` | integer | 码字总数（数据码字 + 纠错码字） |
| `ecCodewords` | integer | 其中的纠错码字数 |
| `size` | integer | 位图边长（像素），与请求的 size 一致 |
| `svg` | string | 完整 SVG 文本，可直接另存为 .svg |
| `pngBase64` | string | PNG 的 base64（不含 data: 前缀），配合 data:image/png;base64, 可预览 |
| `logoAnimated` | boolean | 仅 logo 是动图时出现，固定 true |
| `logoFrames` | integer | 仅 logo 是动图时出现：动图帧数 |

响应示例（`format=json`，取值来自真实请求）：

```jsonc
{
  "text": "https://example.com/",            // 实际编码的内容
  "format": "json",                          // 输出格式，固定 json
  "version": 0,                              // 码格版本 0/1/2，v0 最小
  "level": "Q",                              // 实际纠错等级（容量够时会升级）
  "levelIndex": 2,                           // 等级下标 0=L 1=M 2=Q 3=H
  "mask": 14,                                // 实际掩码 0~31
  "typebits": 122236,                        // 18 位格式信息位
  "mode": "ALPHANUMERIC",                    // 数据模式
  "transformed": "5M +*-F/2IH%%Q53E9:/-IP",  // 仅字母数字模式：码内变换文本
  "codewords": 38,                           // 码字总数（数据+纠错）
  "ecCodewords": 18,                         // 其中纠错码字数
  "size": 600,                               // 位图边长（像素）
  "svg": "<svg width=\"600\" height=\"600\" …>（完整 SVG 文本）",
  "pngBase64": "iVBORw0KGgoAAAANSUhEUg…（PNG 的 base64，不含 data: 前缀）"
}
```

### 错误示例

| 场景 | 状态码 | 响应体 |
|---|---|---|
| 内容装不下 | 400 | `{"error":"载荷超出一枚葵花码容量（v0..v2 均装不下，请缩短内容）"}` |
| 超过接口上限 | 400 | `{"error":"内容过长（上限 8192 字节，葵花码本身也只能装几十字节）"}` |
| text 为空 | 400 | `{"error":"text 不能为空"}` |
| 命中黑名单关键词 | 403 | `{"error":"内容包含禁止发布的词语"}` |
| 超过限速 | 429 | `{"error":"请求过于频繁，请稍后再试"}`，带 `Retry-After` |

---

## 四、POST /api/encode/batch（批量生成）

一次请求生成多个太阳码，适合「给一批链接各生成一个」的场景。单次最多 **50** 条，请求体上限 4MB。

```bash
curl -X POST https://khm.lxwowbp.cn/api/encode/batch \
  -H 'Content-Type: application/json' \
  -d '{"format":"json","size":300,"items":[
        {"text":"https://a.example.com/1"},
        {"text":"https://b.example.com/2"}
      ]}'
```

请求体示例（行尾 `//` 是字段说明）：

```jsonc
{
  "format": "json",                         // 公共格式：建议 json，可一次拿到 svg + pngBase64
  "size": 300,                              // 公共边长，120~2000
  "margin": 0,                              // 公共白边
  "items": [                                // 必填，1~50 条，逐条独立处理
    { "text": "https://a.example.com/1" },  // 第 1 条
    { "text": "https://b.example.com/2" }   // 第 2 条（可用 format/size/logo 覆盖顶层）
  ]
}
```

**顶层是公共参数**（`format` / `size` / `margin` / `fg` / `bg` / `logo` / `logoRatio` / `level` / `version` / `mask` / `badge`），
`items` 里每条只给 `text`；单条也可以用 `format` / `size` / `margin` / `fg` / `bg` / `logo` / `logoRatio` / `badge` 逐条覆盖顶层值。

**逐条独立**：第 3 条失败不影响前 2 条 —— 每条都有自己的 `ok` / `error`。
**逐条计费**：额度按**实际成功且非示例/重复内容**的条数扣（响应里的 `charged` 给出数字），失败的条目不扣。
响应统一是 JSON（`format` 只决定图片怎么出），`format=json` 时同时给 `svg` 与 `pngBase64`。

响应字段：

| 字段 | 类型 | 说明 |
|---|---|---|
| `ok` | integer | 成功的条数 |
| `failed` | integer | 失败的条数 |
| `total` | integer | 提交的总条数（= ok + failed） |
| `charged` | integer | 本次实际扣掉的额度条数；示例 / 重复 / 失败的内容不计 |
| `results` | array | 逐条结果，顺序与请求的 items 一致 |
| `results[].index` | integer | 对应请求 items 的下标，从 0 开始 |
| `results[].text` | string | 该条要编码的内容 |
| `results[].ok` | boolean | 该条是否成功；失败的条目其余字段都不出现 |
| `results[].error` | string | 失败原因；成功时不出现 |
| `results[].version` | integer | 该条的码格版本 |
| `results[].level` | string | 该条实际纠错等级 |
| `results[].mask` | integer | 该条实际掩码 |
| `results[].mode` | string | 该条数据模式 |
| `results[].svg` | string | 该条 SVG 文本；format=svg 或 json 时才有 |
| `results[].pngBase64` | string | 该条图片的 base64；format 不是 svg 时才有 |
| `results[].bytes` | integer | 该条图片字节数；format=svg 时不出现 |
| `results[].ms` | integer | 该条耗时（毫秒） |

响应示例：

```jsonc
{
  "ok": 1,                                      // 成功条数
  "failed": 1,                                  // 失败条数
  "total": 2,                                   // 总条数 = ok + failed
  "charged": 1,                                 // 实际扣的额度（示例/重复/失败不计）
  "results": [                                  // 逐条结果，顺序与 items 一致
    {
      "index": 0,                               // 对应 items 下标，从 0 开始
      "text": "https://a.example.com/1",        // 该条内容
      "ok": true,                               // 该条是否成功
      "version": 0,                             // 该条码格版本
      "level": "M",                             // 该条实际纠错等级
      "mask": 7,                                // 该条实际掩码
      "mode": "ALPHANUMERIC",                   // 该条数据模式
      "svg": "<svg width=\"300\" height=\"300\" …>",  // 该条 SVG（format=svg/json）
      "pngBase64": "iVBORw0KGgoAAAANSUhEUg…",  // 该条图片 base64（非 svg）
      "bytes": 32313,                           // 该条图片字节数
      "ms": 19                                  // 该条耗时（毫秒）
    },
    {
      "index": 1,                               // 第 2 条
      "text": "https://b.example.com/2",
      "ok": false,                              // 失败
      "error": "内容过长（上限 8192 字节）"  // 失败原因
    }
  ]
}
```

---

## 五、GET /api/encode

参数与 POST 完全一致，只是放在 query 里，方便直接嵌入页面：

```html
<img src="https://khm.lxwowbp.cn/api/encode?text=https%3A%2F%2Fexample.com%2F&format=png&size=400">
```

响应体与响应头同 POST。`text` 记得做 URL 编码。

---

## 六、POST /api/decode

解析太阳码。支持三种投喂方式，任选其一：

| 参数 | 位置 | 说明 |
|---|---|---|
| `file` | multipart | 表单文件字段（也接受 `image`）：`-F 'file=@code.png'` |
| （原始二进制） | body | `Content-Type: image/png`，body 就是图片字节 |
| `image` | JSON | `{"image":"data:image/png;base64,..."}` 或 http(s) 链接 |
| `url` | query/JSON | 给图片地址，由服务端下载后解析（**仅公网地址**） |

### 示例

```bash
curl -X POST https://khm.lxwowbp.cn/api/decode -F 'file=@code.png'
curl -X POST https://khm.lxwowbp.cn/api/decode --data-binary @code.png -H 'Content-Type: image/png'
curl 'https://khm.lxwowbp.cn/api/decode?url=https%3A%2F%2Fexample.com%2Fcode.png'
```

JSON 投喂方式的请求示例（行尾 `//` 是说明，不是 JSON 内容）：

```jsonc
{
  "image": "data:image/png;base64,iVBORw0KGgo…",  // 必填其一：data URL / 裸 base64 / http(s) 图片地址
  // 也可以只给图片地址："url": "https://example.com/code.png"
  // 或者根本不发 JSON：直接 multipart -F 'file=@code.png'，或把图片字节当 body 发
}
```

### 返回

| 字段 | 类型 | 说明 |
|---|---|---|
| `family` | string | 码型：WX_CODE 太阳码；family=auto 时也可能是 QR_CODE |
| `version` | integer | 版本号：太阳码 0~2，二维码 1~40 |
| `ecLevel` | string | 纠错等级 L / M / Q / H |
| `mask` | integer | 掩码编号：太阳码 0~31，二维码 0~7 |
| `typebits` | integer | 太阳码的 18 位格式信息位；QR_CODE 时没有该字段 |
| `mode` | string | 数据模式 NUMERIC / ALPHANUMERIC / BYTE；二维码多段时形如 ALPHANUMERIC+BYTE |
| `count` | integer | 数据段声明的字符数；QR_CODE 时没有该字段 |
| `qrText` | string | 仅 ALPHANUMERIC 出现：码内承载的变换文本，调试用 |
| `payload` | string | 解出的最终内容（已做逆变换），业务只需用这个字段 |
| `corrections` | integer | RS 纠错修掉的码字数，0 表示原图很干净 |
| `box` | object | 码在图中的外接圆（归一化 0~1）；定位失败时整个字段不返回 |
| `box.x` | number | 圆心横坐标：0 左边缘、1 右边缘 |
| `box.y` | number | 圆心纵坐标：0 上边缘、1 下边缘 |
| `box.rx` | number | 横向半径，按图片宽度归一化 |
| `box.ry` | number | 纵向半径，按图片高度归一化 |
| `text` | string | 仅 QR_CODE 出现：二维码里的完整文本 |
| `url` | string | 仅 QR_CODE 出现：文本中的链接，没有链接则不存在 |
| `modules` | integer | 仅 QR_CODE 出现：边长模块数 = 17 + 4 × version |

响应示例（真实请求 `https://example.com/` 的 600×600 图）：

```jsonc
{
  "family": "WX_CODE",                  // 码型：WX_CODE / QR_CODE（auto 时）
  "version": 0,                         // 太阳码 0~2
  "ecLevel": "Q",                       // 纠错等级 L/M/Q/H
  "mask": 14,                           // 掩码 0~31
  "typebits": 122236,                   // 18 位格式信息位
  "mode": "ALPHANUMERIC",               // 数据模式
  "count": 23,                          // 声明的字符数
  "qrText": "5M +*-F/2IH%%Q53E9:/-IP",  // 仅字母数字模式：码内变换文本
  "payload": "https://example.com/",    // 解出的最终内容（业务只用这个）
  "corrections": 0,                     // RS 纠错修掉的码字数，0 表示很干净
  "box": {                              // 码在图中的位置（0~1 归一化），定位失败不返回
    "x": 0.499,                         // 圆心横坐标
    "y": 0.499,                         // 圆心纵坐标
    "rx": 0.507,                        // 横向半径
    "ry": 0.507                         // 纵向半径
  }
}
```

### 自动识别（推荐给上传入口）

加 `family=auto` 时只上传一次图片，服务端依次尝试太阳码与普通二维码：

```bash
curl -X POST 'https://khm.lxwowbp.cn/api/decode?family=auto' -F 'file=@unknown.png'
```

好友码入口可加 `prefer=qr` 让普通二维码先试。响应字段是两种结果的并集：
`family` 为 `WX_CODE` 或 `QR_CODE`，`payload` 统一给出可复制内容；
二维码额外有 `text` / `url` / `modules`，太阳码额外有 `typebits` / `count` / `qrText` / `box`。

---

## 七、POST /api/qr

解析普通二维码（微信「加好友」名片即 `https://u.wechat.com/...`）。请求方式与 `/api/decode` 相同。

```bash
curl -X POST https://khm.lxwowbp.cn/api/qr -F 'file=@friend.png'
```

### 返回

| 字段 | 类型 | 说明 |
|---|---|---|
| `text` | string | 二维码里的完整文本 |
| `url` | string | 文本中的 http(s) 链接；没有链接则不存在，可直接丢给 /api/encode |
| `version` | integer | QR 版本 1~40 |
| `ecLevel` | string | 纠错等级 L / M / Q / H |
| `mask` | integer | 掩码 0~7 |
| `mode` | string | 数据模式 NUMERIC / ALPHANUMERIC / BYTE，多段时用 + 连接 |
| `modules` | integer | 边长模块数 = 17 + 4 × version |
| `corrections` | integer | RS 纠错修掉的码字数，0 表示原图很干净 |

响应示例：

```jsonc
{
  "text": "https://u.wechat.com/xxxxxxxxxxxx",  // 二维码里的完整文本
  "url": "https://u.wechat.com/xxxxxxxxxxxx",   // 其中的链接，可直接给 /api/encode
  "version": 4,                                 // QR 版本 1~40
  "ecLevel": "M",                               // 纠错等级
  "mask": 3,                                    // 掩码 0~7
  "mode": "BYTE",                               // 数据模式
  "modules": 33,                                // 模块边长 = 17 + 4 × version
  "corrections": 0                              // RS 修掉的码字数
}
```

---

## 八、Postman 导入

接口页右上角点「Postman」下载，或在 Postman 里选择 **Import → File** 导入 `wxcode.postman_collection.json`。

Postman Collection 只包含公开的生成 / 解析接口。导入后修改两个变量即可：

| 变量 | 说明 |
|---|---|
| `baseUrl` | 站点地址，例如 `https://khm.lxwowbp.cn` |
| `apiKey` | 可选；需要按 Key 计费或本站要求登录时填写「我的」页面复制的 Key |

---

## 九、参数速查

| 参数 | 取值 | 说明 |
|---|---|---|
| `format` | `png` `jpg` `svg` `gif` `json` | 输出格式；`gif` + 动图 logo = 循环动图，最多 20 帧 |
| `level` | `L` `M` `Q` `H` | 最低纠错等级，容量够时自动升级 |
| `version` | `auto` `0` `1` `2` | 码格版本；`auto` 取最小可容纳 |
| `mask` | `-1` 或 `0`~`31` | `-1` 自动按评分选最优 |
| `badge` | `default` `native` `none` 或图片地址 | 角落徽标 |
| `fg` / `bg` | `#rrggbb` | 前景 / 背景色 |

### 容量参考

| 内容类型 | 大约上限 |
|---|---|
| 纯数字 | 约 80 字符 |
| URL / 大写字母数字 | 约 58 字符 |
| 中文 / 小写字母（字节模式） | 约 44 字节 |

---

## 十、前端调用示例

```js
// 生成：直接拿 blob 预览 / 下载
const resp = await fetch('/api/encode', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({text: 'https://example.com/', format: 'png', size: 600})
});
const blob = await resp.blob();
img.src = URL.createObjectURL(blob);
console.log('版本', resp.headers.get('X-WXCode-Version'));

// 解析：上传文件
const fd = new FormData();
fd.append('file', fileInput.files[0]);
const data = await (await fetch('/api/decode', {method: 'POST', body: fd})).json();
console.log(data.payload, data.corrections);
```
---

## 十一、鉴权与额度（生成 / 解析）

| 身份 | 怎么传 | 是否扣额度 |
|---|---|---|
| API Key | `X-API-Key: wx_...`，或 `Authorization: Bearer wx_...`，或 `?key=` | **是**，每次成功调用扣 1 |
| 会话 Cookie | 网页登录后自动携带（`wx_sid`，HttpOnly） | 「使用需要登录」开启时扣；关闭时网页调用不扣 |

优先级：Key > Cookie。带了无效 Key 会直接 401，不会降级成匿名。

计费例外：同一账号对**同一内容**重复生成 / 解析不再扣额度，也不会重复写记录；
`https://example.com/` 这类示例内容与本站地址（首页默认预览）同样不计费、不记录。

额度用尽：`403 {"error":"额度已用完，请联系管理员增加额度"}`；
需要登录：`401 {"error":"该功能需要登录后使用","needLogin":true}`。

