API 文档

面向用户的公共 API

下载接入方式

你可以按实际场景选择下载接入方式。如果你是在网页、官网、论坛、公告页或前端页面里放下载按钮,推荐跳转主站验证页;如果你是在脚本、客户端、CI、自动更新器或后端程序里自动下载文件,使用程序 API 链路。

方式一:跳转主站验证页下载

这种方式适合网页下载按钮。你不需要自己处理挑战、PoW、下载令牌,也不需要关心当前由哪个下载节点提供文件。

需要特别注意:这里要跳转的是主站地址,不是下载节点地址。外部前端应该把用户带到主站的验证页面,由主站完成验证、授权和节点选择,最后再跳转到真实下载节点开始下载。

GET/{project_id}/{version}/{file_name}

验证页地址示例

https://mirror.example.com/fcl/1.3.0.9/FCL-release-1.3.0.9-arm64-v8a.apk

这里的 https://mirror.example.com 应该是主站公共入口,不是某个节点的 public_download_base_url。

如果不知道项目是哪一个,可以先查询项目列表

这个接口可以用来获取 project_id、项目展示名和项目可用状态。前端一般只需要在初始化下载页、项目选择器或外部下载列表时调用它。

GET/api/public/v1/projects

Example Request

GET /api/public/v1/projects
GET/api/public/v1/projects/{project_id}/assets

拿到 project_id 后,前端可以查询该项目下有哪些版本和文件。返回结果里会包含 version、file_name、architecture、system、size_bytes、digest_sha256 和 available 等字段。

Example Request

GET /api/public/v1/projects/example/assets

Example Response

{
  "status": "success",
  "data": {
    "assets": [
      {
        "asset_id": "asset_123",
        "version": "v1.2.3",
        "file_name": "example-windows-amd64.zip",
        "architecture": "amd64",
        "system": "win",
        "available": true
      }
    ]
  }
}

前端可以根据这些字段生成下载按钮。用户点击按钮时,跳转到主站验证页:

https://mirror.example.com/example/v1.2.3/example-windows-amd64.zip
  1. 展示下载验证页面。
  2. 在浏览器中完成下载挑战计算。
  3. 向主节点领取短时下载授权。
  4. 选择可用下载节点。
  5. 跳转到真实下载地址开始下载。

外部网站不要直接拼接节点下载地址,也不要调用程序下载用的 /api/public/v2/api/* 接口来替代这个流程。

方式二:程序调用 API 下载

这种方式适合命令行工具、自动更新器、CI 脚本、下载器或后端服务。程序需要自己查询资产、完成 API PoW 验证,然后携带下载令牌访问下载节点。

第一步:查询项目列表

GET/api/public/v1/projects

查询已启用且至少存在可展示 Release 的项目列表,拿到后续要使用的 project_id。

参数类型描述

Example Request

GET /api/public/v1/projects

Example Response

{
  "status": "success",
  "message": "查询成功",
  "request_id": "req_...",
  "data": {
    "projects": [
      {"project_id": "example", "display_name": "示例项目", "available": true}
    ]
  }
}

第二步:查询项目资产

GET/api/public/v1/projects/{project_id}/assets

程序应选择一个 available=true 的资产,并记录它的 asset_id。如果 available=false,表示当前没有可用下载节点持有这个文件,程序应该稍后重试。

参数类型描述
project_idPath项目标识

Example Request

GET /api/public/v1/projects/example/assets

Example Response

{
  "status": "success",
  "data": {
    "assets": [
      {"asset_id": "asset_123", "file_name": "example.zip", "architecture": "amd64", "system": "win", "available": true}
    ]
  }
}

第三步:创建 API V2 顺序工作量挑战

POST/api/public/v2/api/challenges

为指定资产创建 3072 位 RSA repeated-squaring 挑战。响应中的 modulus 和 base 是 384 字节无符号大端整数的无填充 base64url 编码。

参数类型描述
asset_idJSON要下载的资产标识

Example Request

POST /api/public/v2/api/challenges
{"asset_id":"asset_123"}

Example Response

{
  "status": "success",
  "data": {
    "challenge_id": "challenge_123",
    "algorithm": "rsa-repeated-squaring-v1",
    "modulus_id": "模数标识",
    "modulus": "512 字符 base64url 整数",
    "base": "512 字符 base64url 整数",
    "iterations": 96000,
    "encoding": "base64url-uint-be-384"
  }
}

第四步:顺序计算 solution

从 y = base 开始,严格执行 iterations 次 y = y² mod modulus,再把 y 编码为 384 字节定长大端 base64url。不得提交十进制、十六进制或可变长整数。

展开了解 RSA repeated-squaring 的计算原理

这一步计算的是顺序工作量证明,不是普通密码哈希。挑战给出 RSA 模数 N、起始值 base 和最终迭代数 iterations;客户端只能按顺序使用前一次结果继续模平方。

计算过程

y = decode_unsigned_big_endian(base)
重复 iterations 次:
    y = (y × y) mod N
solution = base64url_no_padding(unsigned_big_endian_384(y))

modulus、base 和 solution 都必须是恰好 384 字节的无符号大端整数,再编码成无填充 base64url,因此线上字符串长度固定为 512。即使结果前面是零,也不能删掉前导零字节。

为什么必须顺序执行

第 i+1 次平方依赖第 i 次的完整结果。数学上最终值等于 base^(2^iterations) mod N,但客户端不知道 RSA 模数的陷门,不能把指数按欧拉函数化简;直接构造 2^iterations 也不能绕过这些依赖。多线程拆分不同区间后无法独立合并,所以应使用单条顺序循环。

服务端如何确认结果

主节点持有只存在于内存中的 RSA 陷门,可以快速得到同一最终值,并在创建挑战时保存定长结果的摘要。授权时服务端先检查挑战版本、算法、来源、资产、客户端前缀、有效期和一次性状态,再校验 0 < solution < N 以及结果摘要。RSA 陷门和预期答案不会发送给客户端或写入 PoW 遥测。

实现时最容易出错的地方

  • 循环次数必须正好等于响应里的最终 iterations,不能使用本地默认值。
  • 每轮都必须先平方再对 N 取模,不能改成哈希、乘以 base 或并行 nonce 搜索。
  • 解码和编码都使用无符号大端;输出必须左侧补零到 384 字节。
  • base64url 使用 -、_ 且不带 = padding。
  • 挑战有有效期并绑定资产和客户端来源;失败后应重新创建挑战,不要跨资产或跨 API 版本复用。

第五步:提交 solution 并领取下载授权

POST/api/public/v2/api/authorizations

提交顺序工作量结果并领取短时、单节点绑定的下载授权。telemetry 可选且只用于统计,不影响授权。API V1 在配置开启时仍保持原 SHA-256 合同。

参数类型描述
challenge_idJSON挑战标识
asset_idJSON资产标识
nonceJSON满足前导零要求的 nonce

Example Request

POST /api/public/v2/api/authorizations
{"challenge_id":"challenge_123","asset_id":"asset_123","solution":"512 字符 base64url 整数"}

Example Response

{
  "status": "success",
  "data": {
    "authorization_id": "auth_123",
    "download_url": "https://node.example/example/v1.2.3/example-windows-amd64.zip",
    "download_token": "43 字符短时随机令牌",
    "expires_at": "2026-05-28T12:05:00Z"
  }
}

第六步:请求下载节点

程序应直接访问授权响应里的 download_url,并通过请求头携带下载令牌。这里的 download_url 通常指向下载节点;程序调用 API 的这条链路里,下载文件时访问节点地址是正确的。

GET{download_url}

Example Request

GET {download_url}
Authorization: Bearer <download_token>

如果需要断点续传,可以使用单段 Range:

GET {download_url}
Authorization: Bearer <download_token>
Range: bytes=1048576-2097151

其他接口

下面这些接口不是程序下载流程的步骤,只用于订阅、状态查询或排障。

封禁列表订阅

GET/api/public/v1/blocklist.txt
GET/api/public/v1/blocklist.json

返回当前生效的公共下载封禁列表。内容包含 quota.yaml 静态黑名单和本站手动/自动封禁记录,不包含远程订阅源快照,响应在服务端缓存 60 秒。

TXT Example Response

# [枫源镜像封禁] 封禁原因: traffic_limit_exceeded, 来源: local_auto_ban, 封禁后尝试次数: 3
2.59.169.232

JSON Example Response

{
  "status": "success",
  "data": {
    "blocks": [
      {"entry": "2.59.169.232", "reason": "traffic_limit_exceeded", "attempts_after_block": 3, "blocked_at": "2026-06-21T12:00:00Z"}
    ]
  }
}

更新日志

GET/api/public/v1/changelog

按时间倒序查询本站更新记录。minimum_level 可选 info、notice、warn 或 critical;q 搜索标题和可见描述;limit 默认 20、最大 50。存在下一批时响应返回不透明的 next_cursor。

Example Request

GET /api/public/v1/changelog?minimum_level=notice&q=下载&limit=20
GET/api/public/v1/authorizations/{authorization_id}

携带对应下载令牌查询授权状态、过期时间、公开节点名和已入账真实发送字节。

参数类型描述
authorization_idPath授权标识
AuthorizationHeaderBearer <download_token>

Example Request

GET /api/public/v1/authorizations/auth_123
Authorization: Bearer <download_token>

Example Response

{
  "status": "success",
  "data": {
    "authorization_id": "auth_123",
    "state": "issued",
    "bytes_accounting_enabled": true,
    "sent_bytes": 1048576
  }
}