# 本站 官网使用手册

- 本手册最新版：https://www.buytokenhere.com/api/public/site-guide
- hub 地址：https://www.buytokenhere.com
- hub 版本：e781ccc5
- 生成时间：2026-11-04T03:11:03.059Z（UTC）

> 给 AI 的说明：这份手册由上面这个 hub 实例实时生成，内容与该实例当前运行的代码版本一致。每次动手修改官网前，先重新读取上面的「本手册最新版」地址，以它为准；读不到（旧版本 hub 返回 404、断网等）时，才使用官网模板包里的 `docs/site-guide.md` 副本。不同 hub 实例的版本可能不同，只信你要发布的那个实例的手册。

手册分两部分：A 讲官网模板怎么用、怎么发布；B 讲官网页面可以调用哪些免登录接口。

---

## A. 官网模板怎么用

### A1. 这是什么

官网是一个纯静态网站（HTML/CSS/JS + 图片），打成 zip 上传到 hub 后台后，直接用 hub 的域名对外访问：首页是 `https://www.buytokenhere.com/`，其他页面在 `https://www.buytokenhere.com/discover/...`。官网和 hub 同域名，登录、注册、控制台仍由 hub 提供。

官网模板包在 hub 后台「系统 → 官网管理 → 官网模板包」下载，下载时 `.env.example` 里的 `HUB_URL` 已自动填成当前访问的域名，`docs/site-guide.md` 是下载那一刻本手册的副本。

### A2. 目录与路径规则

| 位置 | 作用 | 能不能改 |
| --- | --- | --- |
| `index.html` | 首页，访问 `/` 时显示 | 可以改内容，文件名不能改 |
| `discover/` | 其他所有页面和资源（CSS、JS、图片、字体） | 随便加页面、加资源 |
| `discover/assets/site.css` | 所有页面共用的样式 | 可以改 |
| `robots.txt`、`sitemap.xml`、`llms.txt`、`favicon.ico` | 根目录 GEO 文件和网站图标 | 可以改内容，文件名不能改 |
| `AGENTS.md`、`GEO-CHECKLIST.md`、`skills/` | 给 AI 的规则、自查清单、发布技能 | 一般不用改 |
| `scripts/` | 打包、发布脚本 | 不要改 |
| `本地预览/` | 双击预览脚本 | 不要改 |
| `docs/` | 本手册的本地副本 | 不要改，重新下载模板包即可更新 |
| `.env.example` / `.env` | hub 地址和发布令牌 | 只改 `.env` |

规则：

- 上传的 zip 根目录**只允许** `index.html`、`robots.txt`、`sitemap.xml`、`llms.txt`、`favicon.ico` 五个文件和 `discover/` 文件夹，其他任何根目录文件或文件夹都会被整包拒收。`scripts/pack.sh` 打包时会自动排除 `AGENTS.md`、`README.md`、`GEO-CHECKLIST.md`、`skills/`、`scripts/`、`本地预览/`、`docs/`、`dist/`、`.env*` 等本地文件。
- 新页面用「文件夹 + index.html」：`discover/xxx/index.html`，访问地址 `/discover/xxx/`。
- 站内链接和资源一律写以 `/` 开头的绝对路径：页面和资源用 `/discover/...`，首页用 `/`，根目录文件用 `/robots.txt` 等。官网的页面可能在任意层级，相对路径容易错；hub 的其他路径（`/api`、`/v1`、`/assets` 等）属于 hub，官网不能占用。
- 登录按钮链接 `/login`，注册按钮链接 `/register`（这两个是 hub 的页面，只能链接，不能在官网里创建）。
- 默认是纯静态，不需要构建。若改用 Vue/React 等框架，构建配置的 `base` 必须是 `/discover/`，只上传构建产物。
- zip 里 `discover/` 下的文件扩展名必须在后台扩展名白名单内（默认：html、htm、css、js、mjs、json、xml、txt、md、svg、png、jpg、jpeg、gif、webp、avif、ico、woff、woff2、ttf、otf、webmanifest、pdf、mp4、webm；管理员可在官网管理页调整）。php、py、sh、exe、bat、ps1 等可执行类型永远不允许。

### A3. 本地预览

页面路径以 `/` 开头，直接双击 `index.html` 显示不正常，要用本地服务预览：

- Mac：双击 `本地预览/双击预览（Mac）.command`
- Windows：双击 `本地预览/双击预览（Windows）.bat`（需要装有 Python 3）
- 也可以在模板根目录手动运行 `python3 -m http.server 8000`，再打开 `http://localhost:8000/`

脚本会在本地起服务（端口 8000；Mac 版遇到端口被占用会自动换下一个）并打开浏览器，关掉弹出的窗口即停止。登录、注册是 hub 的页面，本地预览时打不开，属于正常现象。

### A4. 发布令牌与 .env

1. 管理员在 hub 后台「系统 → 官网管理」的「发布令牌」卡片点「生成令牌」，令牌以 `hos_` 开头，**只显示一次**，立即复制。
2. 把 `.env.example` 复制成 `.env`，把令牌填到 `SITE_TOKEN=` 后面；`HUB_URL` 已自动填好，不要加结尾的 `/`。
3. 令牌只能上传和回滚官网，不能访问 hub 其他接口。「重新生成」后旧令牌立即失效；「作废」后所有脚本都无法发布。
4. 令牌泄露时，到同一位置点「重新生成」，再更新 `.env`。不要把令牌写进页面、代码、日志或 git 提交（模板的 `.gitignore` 已忽略 `.env`）。

### A5. 打包

```bash
bash scripts/pack.sh                 # Mac / Linux
powershell -File scripts/pack.ps1    # Windows
```

脚本先检查根目录，有不允许的文件或文件夹就报错退出并列出名字；通过后把根目录白名单文件和 `discover/` 打成 `dist/site-<时间>.zip`，超过 20MB 报错。

### A6. 发布与回滚

```bash
bash scripts/publish.sh                       # 上传 dist/ 下最新的 zip
bash scripts/publish.sh --note "首页改版"      # 带版本备注（≤200 字）
bash scripts/publish.sh --rollback            # 回滚到上一个版本
bash scripts/publish.sh --rollback 12         # 回滚到指定版本号（releaseId）
```

Windows：`powershell -File scripts/publish.ps1 -Note "备注"`、`-Rollback`、`-Rollback -ReleaseId 12`。

上传成功返回 `releaseId`、`version`、`fileCount`、`totalBytes`、`warnings`（GEO 警告）等。常见失败：

| HTTP | 原因 | 怎么办 |
| --- | --- | --- |
| 400 | zip 校验未通过，`data.details` 逐条列出 `path` 和 `message` | 按提示修正后重新打包上传 |
| 401 | 令牌无效、已重新生成或已作废 | 重新生成令牌并更新 `.env` |
| 409 | 另一个上传/回滚/删除正在进行；或没有可回滚的版本 | 稍后重试；回滚需要至少有一个历史版本 |
| 413 | zip 超过 20MB | 压缩图片、删减资源 |

后台保留所有历史版本，管理员也可以在官网管理页回滚、删除旧版本（当前线上版本不能删）、修改备注。

### A7. 限额与校验

- zip 不超过 20MB；解压后总大小不超过 100MB；最多 2000 个文件；单个 ≥64KB 的文件压缩比超过 100 倍按压缩炸弹拒收。
- **结构问题整包拒收**：根目录有多余文件、缺 `index.html`（常见原因是 zip 外面多套了一层文件夹）、扩展名不在白名单、绝对路径或 `..`、符号链接、重复条目、超出限额。
- **GEO 问题只警告不拒收**：缺 `sitemap.xml`/`robots.txt`/`llms.txt`；`robots.txt` 屏蔽了 GPTBot、ClaudeBot、PerplexityBot、Google-Extended；页面缺 `<title>`、`meta description`、`canonical`；`<h1>` 不是正好 1 个；首页缺 JSON-LD；站内引用没用 `/discover/` 前缀（`/`、`/login`、`/register` 和根目录文件除外）。

### A8. 上线后

- 上传成功即切换到新版本，官网文件按 `Cache-Control: no-cache` 返回，刷新就能看到新版本；仍看到旧页面时，先确认上传返回成功，再强制刷新，或检查 CDN（如 Cloudflare）是否对页面做了额外缓存。
- 还没上传过官网时，访问 `/` 显示 hub 自带的默认页，并带 `noindex`，不会被搜索引擎收录。
- `/api/` 下的接口统一带 `X-Robots-Tag: noindex`，不会被收录，所以官网要展示的数据必须写进页面 HTML（见 B0）。

---

## B. 官网可以调用的公开接口

### B0. 通用说明

- 所有接口都在 `https://www.buytokenhere.com/api` 下，免登录，只读，返回 JSON（本手册接口除外）。
- **同域调用没有跨域问题**：官网和 hub 同域名，页面里直接 `fetch('/api/...')` 即可。从其他域名调用会被拒绝（403，提示来源域名未加入 `CORS_ORIGINS`），除非 hub 管理员把该域名加进后端环境变量 `CORS_ORIGINS`。
- **推荐在打包前取数据、写进静态 HTML**：AI 爬虫和很多搜索引擎不执行 JS，用 JS 拉数据渲染的内容它们看不到；而且部分接口有 IP 限流。做法：打包前用 `curl` 或脚本取一次数据，把价格、模型列表等直接写进页面正文，并在页面写「数据更新于 YYYY-MM-DD」。浏览器端 JS 只用于「实时状态」这类非关键的小组件，接口失败时页面也要能正常阅读。
- 限流：标注了限流的接口按访问者 IP 计数，超过返回 `429`，`message` 为「请求过于频繁，请稍后再试」。
- 出错时统一返回 `{"code": <HTTP 状态码>, "message": "中文原因", "data": null}`。
- 登录、注册直接链接 hub 页面 `/login`、`/register`，不要在官网里调用登录注册接口。
- 下面的示例响应都做了裁剪，数值仅作示意，以实际返回为准。
- 价格类字段都是**字符串**形式的定点小数（8 位小数），单位是**美元 / 百万 tokens**，显示前用 `Number()` 转换并自行保留位数。

### GET /api/brand-settings

品牌信息：品牌名、站点标题、logo / 网站图标 / 客服二维码地址。不限流，响应 `Cache-Control: no-store`。

参数：无。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `brandName` | string \| null | 品牌名，未配置为 null |
| `siteTitle` | string \| null | 站点标题，未配置为 null |
| `logoUrl` | string \| null | logo 地址（相对路径，带 `?v=` 版本参数），未上传为 null |
| `faviconUrl` | string \| null | 网站图标地址，未上传为 null |
| `wechatQrUrl` | string \| null | 客服微信二维码地址，未上传为 null |

```bash
curl -s https://www.buytokenhere.com/api/brand-settings
```

```json
{
  "brandName": "示例品牌",
  "logoUrl": "/api/brand-settings/assets/logo?v=2026-10-01T08%3A00%3A00.000Z",
  "siteTitle": "示例品牌 - AI 接口服务",
  "faviconUrl": null,
  "wechatQrUrl": "/api/brand-settings/assets/wechat-qr?v=2026-10-01T08%3A00%3A00.000Z"
}
```

### GET /api/brand-settings/assets/:slot

读取品牌图片。不限流，响应 `Cache-Control: public, max-age=0, must-revalidate`。

| 参数 | 位置 | 说明 |
| --- | --- | --- |
| `slot` | 路径 | `logo`、`favicon`、`wechat-qr` 三选一，其他值返回 400 |
| `v` | query，可选 | 版本参数，直接使用 `/api/brand-settings` 返回的完整地址即可，图片更新后地址会变 |

返回图片本身（PNG、JPEG、GIF、WebP 或 ICO），未上传返回 404「品牌资源未配置」。官网里可以直接 `<img src="/api/brand-settings/assets/logo" alt="本站 logo">`；要长期稳定，更推荐把图片下载下来放进 `discover/assets/`。

```bash
curl -s -o logo.png https://www.buytokenhere.com/api/brand-settings/assets/logo
```

### GET /api/system-settings

站点开关：是否开放注册等。不限流，响应 `Cache-Control: no-store`。

参数：无。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `registrationEnabled` | boolean | 是否开放注册，未配置时为 true；为 false 时官网应隐藏或置灰「注册」按钮 |
| `mainPlatformDomain` | string \| null | 主平台（API 调用）的域名，只有主机名，未配置为 null |
| `redemptionPurchaseUrl` | string \| null | 兑换码购买页地址，未配置为 null |
| `loginPageStyle` | string | hub 登录页风格：`classic`、`nebula`、`amber`（默认） |

```bash
curl -s https://www.buytokenhere.com/api/system-settings
```

```json
{
  "registrationEnabled": true,
  "mainPlatformDomain": "api.example.com",
  "redemptionPurchaseUrl": null,
  "loginPageStyle": "amber"
}
```

### GET /api/public/model-plaza

模型广场：当前可用的模型和最低价。限流：每 IP 每分钟 30 次；服务端缓存 5 分钟。

参数：无。

数据口径：只统计主平台上状态正常、按量计费（非订阅）、非专属、有测试记录且最近一次测试不是失败的分组；模型来自这些分组账号的模型映射，只列出能查到官方价格的模型。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `generatedAt` | string | 数据生成时间（ISO 8601，UTC） |
| `items[]` | array | 模型列表，按厂商（openai → anthropic → grok → gemini → 其他）再按名称排序 |
| `items[].name` | string | 模型名 |
| `items[].platform` | string | 厂商，如 `openai`、`anthropic`、`gemini` |
| `items[].contextTokens` | number \| null | 上下文长度（tokens），未知为 null |
| `items[].inputPricePerMillion` | string | 本站最低输入价，美元 / 百万 tokens |
| `items[].outputPricePerMillion` | string | 本站最低输出价，美元 / 百万 tokens |
| `items[].officialInputPricePerMillion` | string | 官方输入价（未打折），美元 / 百万 tokens |
| `items[].officialOutputPricePerMillion` | string | 官方输出价（未打折），美元 / 百万 tokens |
| `items[].cacheReadPricePerMillion` | string \| null | 缓存命中价，美元 / 百万 tokens；模型没有缓存价时为 null |
| `items[].cheapestGroup` | string | 最低价所在分组名 |
| `items[].groups[]` | array | 所有可用分组的价格，按价格从低到高，第一个就是 `cheapestGroup` |
| `items[].groups[].name` | string | 分组名 |
| `items[].groups[].inputPricePerMillion` / `outputPricePerMillion` / `cacheReadPricePerMillion` | string / string / string \| null | 该分组的输入 / 输出 / 缓存命中价 |

折扣可以用 `inputPricePerMillion ÷ officialInputPricePerMillion` 自行计算。

```bash
curl -s https://www.buytokenhere.com/api/public/model-plaza
```

```json
{
  "generatedAt": "2026-10-18T03:00:00.000Z",
  "items": [
    {
      "name": "gpt-5",
      "platform": "openai",
      "contextTokens": 400000,
      "inputPricePerMillion": "0.62500000",
      "outputPricePerMillion": "5.00000000",
      "officialInputPricePerMillion": "1.25000000",
      "officialOutputPricePerMillion": "10.00000000",
      "cacheReadPricePerMillion": "0.06250000",
      "cheapestGroup": "OpenAI 特价",
      "groups": [
        { "name": "OpenAI 特价", "inputPricePerMillion": "0.62500000", "outputPricePerMillion": "5.00000000", "cacheReadPricePerMillion": "0.06250000" }
      ]
    }
  ]
}
```

### GET /api/public/group-ranking

分组排行：所有公开分组及其测速数据。限流：每 IP 每分钟 30 次；服务端缓存 5 分钟。

参数：无（带任何 query 参数返回 400）。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `generatedAt` | string | 数据生成时间（ISO 8601，UTC） |
| `groups[]` | array | 公开分组，按厂商 → 倍率 → 名称排序 |
| `groups[].upstreamGroupId` | number | 分组 ID，下面两个分组接口的路径参数 |
| `groups[].name` | string | 分组名 |
| `groups[].remark` | string \| null | 分组说明 |
| `groups[].platform` | string | 厂商，如 `openai`、`anthropic`、`gemini`、`antigravity` |
| `groups[].subscriptionType` | string | `standard`（按量）或 `subscription`（订阅） |
| `groups[].rateMultiplier` | number | 计费倍率：实际价 = 官方价 × 倍率，如 0.5 表示五折 |
| `groups[].effectiveMultiplier` | number | 兼容字段，始终等于 `rateMultiplier` |
| `groups[].isExclusive` | boolean | 是否专属分组 |
| `groups[].status` | string | 分组状态，公开列表里都是 `active` |
| `groups[].isFeatured` | boolean | 是否精选分组 |
| `latency[]` | array | 有测试记录的分组的测速汇总 |
| `latency[].upstreamGroupId` | number | 分组 ID |
| `latency[].latestStatus` | string | 最近一次测试结果：`success`、`error`、`running`、`skipped` |
| `latency[].latest3` | object | 最近 3 次成功测试中最快的一次 |
| `latency[].lastHour` | object | 最近 1 小时成功测试的平均值 |
| `latency[].last24h` | object | 最近 24 小时成功测试的平均值 |
| `latency[].*.sampleCount` | number | 有总耗时的样本数 |
| `latency[].*.durationMsAvg` | number \| null | 总耗时（毫秒），无样本为 null |
| `latency[].*.firstTokenSampleCount` | number | 有首字耗时的样本数 |
| `latency[].*.firstTokenMsAvg` | number \| null | 首字耗时（毫秒），无样本为 null |

```bash
curl -s https://www.buytokenhere.com/api/public/group-ranking
```

```json
{
  "generatedAt": "2026-10-18T03:00:00.000Z",
  "groups": [
    {
      "upstreamGroupId": 12,
      "name": "Claude 稳定",
      "remark": "官方直连",
      "platform": "anthropic",
      "subscriptionType": "standard",
      "rateMultiplier": 0.6,
      "effectiveMultiplier": 0.6,
      "isExclusive": false,
      "status": "active",
      "isFeatured": true
    }
  ],
  "latency": [
    {
      "upstreamGroupId": 12,
      "latestStatus": "success",
      "latest3": { "sampleCount": 3, "durationMsAvg": 1820, "firstTokenSampleCount": 3, "firstTokenMsAvg": 640 },
      "lastHour": { "sampleCount": 4, "durationMsAvg": 2105, "firstTokenSampleCount": 4, "firstTokenMsAvg": 702 },
      "last24h": { "sampleCount": 96, "durationMsAvg": 2310, "firstTokenSampleCount": 96, "firstTokenMsAvg": 755 }
    }
  ]
}
```

### GET /api/public/group-ranking/groups/:upstreamGroupId/test-logs

某个公开分组最近 15 天的测试记录，分页。限流：每 IP 每分钟 30 次（与分组排行共用计数）。

| 参数 | 位置 | 说明 |
| --- | --- | --- |
| `upstreamGroupId` | 路径 | 正整数，来自 `/api/public/group-ranking` 的 `groups[].upstreamGroupId` |
| `page` | query，可选 | 页码，≥1，默认 1 |
| `pageSize` | query，可选 | 每页条数，1–100，默认 20 |

分组不存在或不公开返回 404「分组不存在」；其他 query 参数返回 400。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `items[]` | array | 测试记录，按开始时间从新到旧 |
| `items[].id` | number | 记录 ID |
| `items[].status` | string | `success`、`error`、`running`、`skipped` |
| `items[].triggerType` | string | `scheduled`（定时）或 `manual`（手动） |
| `items[].modelId` | string \| null | 测试用的模型 |
| `items[].durationMs` | number \| null | 总耗时（毫秒） |
| `items[].firstTokenMs` | number \| null | 首字耗时（毫秒） |
| `items[].errorMessage` | string \| null | 失败原因摘要（已脱敏） |
| `items[].startedAt` | string | 开始时间（ISO 8601，UTC） |
| `items[].finishedAt` | string \| null | 结束时间，未结束为 null |
| `total` | number | 总条数 |
| `page` / `pageSize` / `totalPages` | number | 当前页、每页条数、总页数 |

```bash
curl -s "https://www.buytokenhere.com/api/public/group-ranking/groups/12/test-logs?page=1&pageSize=20"
```

```json
{
  "items": [
    {
      "id": 90321,
      "status": "success",
      "triggerType": "scheduled",
      "modelId": "claude-sonnet-4-5",
      "durationMs": 1820,
      "firstTokenMs": 640,
      "errorMessage": null,
      "startedAt": "2026-10-18T02:55:00.000Z",
      "finishedAt": "2026-10-18T02:55:02.000Z"
    }
  ],
  "total": 1436,
  "page": 1,
  "pageSize": 20,
  "totalPages": 72
}
```

### GET /api/public/group-ranking/groups/:upstreamGroupId/supported-models

某个公开分组支持的模型和价格。限流：每 IP 每分钟 30 次（与分组排行共用计数）。

| 参数 | 位置 | 说明 |
| --- | --- | --- |
| `upstreamGroupId` | 路径 | 正整数，同上 |

不接受 query 参数；分组不存在或不公开返回 404「分组不存在」。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `generatedAt` | string | 生成时间（ISO 8601，UTC） |
| `items[]` | array | 模型列表，按输入价从低到高；只列出能查到官方价格的模型 |
| `items[].name` | string | 模型名 |
| `items[].platform` | string | 厂商 |
| `items[].officialInputPricePerMillion` / `officialOutputPricePerMillion` | string | 官方输入 / 输出价，美元 / 百万 tokens |
| `items[].inputPricePerMillion` / `outputPricePerMillion` | string | 该分组输入 / 输出价（官方价 × 分组倍率），美元 / 百万 tokens |
| `items[].cacheReadPricePerMillion` | string \| null | 该分组缓存命中价，没有缓存价为 null |

```bash
curl -s https://www.buytokenhere.com/api/public/group-ranking/groups/12/supported-models
```

```json
{
  "generatedAt": "2026-10-18T03:00:00.000Z",
  "items": [
    {
      "name": "claude-sonnet-4-5",
      "platform": "anthropic",
      "officialInputPricePerMillion": "3.00000000",
      "officialOutputPricePerMillion": "15.00000000",
      "inputPricePerMillion": "1.80000000",
      "outputPricePerMillion": "9.00000000",
      "cacheReadPricePerMillion": "0.18000000"
    }
  ]
}
```

### GET /api/referral/public-rules

邀请返利规则（默认等级）。不限流。

参数：无。

| 字段 | 类型 | 含义 |
| --- | --- | --- |
| `rules` | object \| null | 没有配置默认等级时为 null，官网应隐藏返利介绍 |
| `rules.name` | string | 等级名称 |
| `rules.inviterCommissionRate` | number | 邀请人返利比例，0–1 的小数，0.1 表示 10% |
| `rules.secondLevelCommissionRate` | number | 二级返利比例（邀请人的邀请人），0–1 的小数 |
| `rules.inviteeBonusRate` | number | 被邀请人充值优惠比例，0–1 的小数 |

```bash
curl -s https://www.buytokenhere.com/api/referral/public-rules
```

```json
{
  "rules": {
    "name": "默认",
    "inviterCommissionRate": 0.1,
    "secondLevelCommissionRate": 0.02,
    "inviteeBonusRate": 0.05
  }
}
```

### GET /api/public/site-guide

本手册。返回 Markdown（`Content-Type: text/markdown; charset=utf-8`，`Cache-Control: no-cache`）。限流：每 IP 每分钟 30 次。

参数：无。手册里的域名按访问时的域名填写，品牌名取自品牌设置，版本号是该实例构建时的 commit。旧版本 hub 没有这个接口，会返回 404，此时请用模板包里的 `docs/site-guide.md`。

```bash
curl -s https://www.buytokenhere.com/api/public/site-guide
```
