API Docs
Public download API documentation for web pages, scripts, clients and automatic updaters.
Download integration
Choose the download integration that fits your use case. For download buttons on web pages, homepages, forums, announcements or front-end pages, redirect to the main-site verification page. For scripts, clients, CI, auto-updaters or backend services, use the programmatic API flow.
Before integrating: stability and conventions
Only endpoints explicitly documented here are part of the stable third-party integration contract. New clients should prefer the project/asset lookup endpoints and download authorization protocol V2. A publicly reachable endpoint that is not documented here must not be treated as a long-term compatibility promise.
Stable developer endpoints
GET /api/public/v1/projects
GET /api/public/v1/projects/{project_id}/assets
POST /api/public/v2/api/challenges
POST /api/public/v2/api/authorizations
GET /api/public/v2/authorizations/{authorization_id}
GET /api/public/v1/blocklist.txt
GET /api/public/v1/blocklist.json
GET /api/public/v1/changelog
POST /api/developer/v1/projects/{project_id}/sync
The term “download authorization protocol V1” refers only to the legacy /api/public/v1/api/challenges and /api/public/v1/api/authorizations PoW/authorization endpoints. It does not mean that the entire /api/public/v1/ namespace is deprecated. V1 resource endpoints for projects, assets, blocklists and changelog remain current stable APIs.
Publicly reachable site-internal endpoints
/api/public/v1/catalog, /api/public/v1/stats, /api/public/v1/stats/details, /api/public/v2/web/* and web-verification endpoints are used by this site's front end or internal displays. They are publicly reachable but are not currently a stable third-party API contract, and their fields or behavior may change with the site implementation. Third-party programs should not depend on them.
JSON response envelope
Except for the TXT blocklist feed, download-node file responses and the site-internal data endpoints called out above, JSON APIs documented here use a common envelope. Read data on success; on failure branch on the HTTP status and stable code, and retain request_id for troubleshooting.
{
"status": "success",
"message": "...",
"request_id": "req_...",
"data": {}
}
{
"status": "error",
"message": "...",
"request_id": "req_...",
"code": "STABLE_ERROR_CODE"
}
POST endpoints use JSON request bodies. Explicitly send Content-Type: application/json; public download API JSON bodies are limited to 16 KiB.
Rate limits, retries and errors
Public endpoints are protected by configurable source-level resource limits and download abuse controls. For 429 or 503 responses, honor Retry-After when present instead of polling at a fixed high rate. The Developer API additionally returns X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset.
| HTTP | code | Description |
|---|---|---|
| 401 | DOWNLOAD_TOKEN_INVALID | The download token is invalid, expired or does not belong to the source querying this authorization. |
| 403 | CHALLENGE_FAILED / CLIENT_BLOCKED | The challenge does not match, validation failed, or the current source is blocked. |
| 409 | NO_ROUTABLE_NODE / CHALLENGE_IN_PROGRESS | No routable node is available, or the same challenge is still being processed. |
| 410 | API_VERSION_RETIRED | The legacy download authorization protocol V1 is disabled in the current deployment. |
| 429 | PUBLIC_RESOURCE_RATE_LIMITED / CLIENT_RATE_LIMITED / CHALLENGE_RATE_LIMITED / REQUEST_QUOTA_EXHAUSTED / TRAFFIC_LIMIT_EXCEEDED | A request, challenge or traffic quota was limited; honor Retry-After when present. |
| 503 | CHALLENGE_CAPACITY_REACHED / VDF_BUSY | The challenge service is temporarily busy and normally returns Retry-After. |
| 500 | PUBLIC_INTERNAL_ERROR | Internal server error; retain request_id before retrying or reporting the issue. |
Client-source binding
Challenge creation and authorization submission must be performed from the same client network prefix recognized by the server. Authorization-status queries must also use the source prefix that received the authorization. Do not split challenge creation, solution submission and status polling across machines with different egress IPs. After authorization succeeds, download_token is a Bearer credential; the actual download-node request may use a different egress, so protect the token like a password.
Method 1: redirect to the main-site verification page
This method is suitable for web download buttons. You do not need to handle challenges, PoW or download tokens yourself, and you do not need to know which download node currently serves the file.
Important: redirect to the main-site address, not a download-node address. External front ends should send users to the main-site verification page, where verification, authorization and node selection are completed before redirecting to the actual download node.
/{project_id}/{version}/{file_name}Verification URL example
https://fyhub.cn/fcl/1.3.0.9/FCL-release-1.3.0.9-arm64-v8a.apk
https://fyhub.cn is the public entry point of Maple Mirror, not a download node's public_download_base_url.
If you do not know the project, query the project list first
This endpoint returns project_id, the project display name and availability. Front ends usually call it while initializing a download page, project selector or external download list.
/api/public/v1/projectsExample Request
GET /api/public/v1/projects
/api/public/v1/projects/{project_id}/assetsAfter obtaining project_id, query the versions and files for that project. The response includes fields such as version, file_name, architecture, system, size_bytes, digest_sha256 and 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
}
]
}
}
The front end can use these fields to create a download button. When the user clicks it, redirect to the main-site verification page:
https://fyhub.cn/example/v1.2.3/example-windows-amd64.zip
- Show the download verification page.
- Solve the download challenge in the browser.
- Obtain short-lived download authorization from the main node.
- Choose an available download node.
- Redirect to the real download URL.
External sites must not construct node download URLs directly or replace this flow with the programmatic /api/public/v2/api/* endpoints.
Method 2: download through the programmatic API
This method is suitable for command-line tools, auto-updaters, CI scripts, downloaders and backend services. The program must query assets, complete API PoW verification and then use the download token to access a download node.
Step 1: query the project list
/api/public/v1/projectsQuery the currently enabled projects and obtain project_id. A project may still appear while no routable download asset is available, so use available to decide whether it can be downloaded now.
| Parameter | Type | Description |
|---|---|---|
| None | ||
Example Request
GET /api/public/v1/projects
Example Response
{
"status": "success",
"message": "查询成功",
"request_id": "req_...",
"data": {
"projects": [
{
"project_id": "example",
"repository": "owner/example",
"display_name": "示例项目",
"description": "示例说明",
"homepage_url": "https://fyhub.cn",
"available": true
}
]
}
}
Step 2: query project assets
/api/public/v1/projects/{project_id}/assetsChoose an asset with available=true and record its asset_id. If available=false, no available download node currently holds the file, so retry later.
| Parameter | Type | Description |
|---|---|---|
| project_id | Path | Project identifier |
Example Request
GET /api/public/v1/projects/example/assets
Example Response
{
"status": "success",
"data": {
"assets": [
{
"asset_id": "asset_123",
"version": "v1.2.3",
"download_path": "/example/v1.2.3/example.zip",
"prerelease": false,
"file_name": "example.zip",
"architecture": "amd64",
"system": "win",
"size_bytes": 10485760,
"digest_sha256": "0123456789abcdef...",
"available": true,
"unavailable_reason": ""
}
]
}
}
Step 3: create an API V2 sequential-work challenge
Legacy “download authorization protocol V1” deprecation notice: only /api/public/v1/api/challenges and /api/public/v1/api/authorizations use SHA-256 leading-zero nonce search, and a deployment may already disable them and return 410 API_VERSION_RETIRED. This does not deprecate project, asset, blocklist or changelog endpoints under /api/public/v1/. New clients must implement the V2 repeated-squaring flow below rather than only replacing endpoint paths.
/api/public/v2/api/challengesCreate a 3072-bit RSA repeated-squaring challenge for an asset. The modulus and base in the response are unpadded base64url encodings of 384-byte unsigned big-endian integers.
| Parameter | Type | Description |
|---|---|---|
| asset_id | JSON | Asset to download |
Example Request
POST /api/public/v2/api/challenges
{"asset_id":"asset_123"}
Example Response
{
"status": "success",
"data": {
"challenge_id": "challenge_123",
"asset_id": "asset_123",
"algorithm": "rsa-repeated-squaring-v1",
"modulus_id": "模数标识",
"modulus": "512 字符 base64url 整数",
"base": "512 字符 base64url 整数",
"iterations": 96000,
"encoding": "base64url-uint-be-384",
"expires_at": "2026-09-03T12:05:00Z"
}
}
Step 4: compute the solution sequentially
Starting from y = base, perform y = y² mod modulus exactly iterations times, then encode y as a fixed-length 384-byte big-endian base64url value. Do not submit a decimal, hexadecimal or variable-length integer.
Learn how RSA repeated-squaring works
This step computes a sequential proof of work, not an ordinary password hash. The challenge provides an RSA modulus N, a starting base and a final iteration count; the client must continue modular squaring in order.
Computation
y = decode_unsigned_big_endian(base)
重复 iterations 次:
y = (y × y) mod N
solution = base64url_no_padding(unsigned_big_endian_384(y))
modulus, base and solution must each be exactly 384-byte unsigned big-endian integers encoded as unpadded base64url, so the wire string is always 512 characters. Leading zero bytes must be retained.
Why execution must be sequential
Each square depends on the complete result of the previous one. Mathematically the final value is base^(2^iterations) mod N, but the client does not know the RSA trapdoor and cannot reduce the exponent with Euler's function. Constructing 2^iterations does not bypass the dependencies. Separate ranges cannot be merged independently, so use one sequential loop.
How the server verifies the result
The main node keeps an in-memory RSA trapdoor and can compute the same final value quickly. It stores a digest of the fixed-length result when creating the challenge. During authorization it checks the challenge version, algorithm, source, asset, client prefix, expiry and one-time state, then validates 0 < solution < N and the digest. The trapdoor and expected answer are never sent to the client or written to PoW telemetry.
Common implementation pitfalls
- The loop count must exactly equal the final iterations in the response; do not use a local default.
- Each round must square first and then reduce modulo N; do not replace it with hashing, multiplication by base or parallel nonce search.
- Decode and encode as unsigned big-endian; left-pad the output to 384 bytes.
- Use - and _ for base64url and omit = padding.
- Challenges expire and bind the asset and client source. Create a new challenge after failure; do not reuse one across assets or API versions.
# Mirror Server API V2 repeated-squaring 原理
## 用途
程序下载在调用 `POST /api/public/v2/api/challenges` 后,需要根据响应计算 `solution`,再提交到 `POST /api/public/v2/api/authorizations`。这是顺序工作量证明,不是 SHA 哈希 nonce 搜索。
## 挑战输入
- `algorithm` 必须是 `rsa-repeated-squaring-v1`。
- `encoding` 必须是 `base64url-uint-be-384`。
- `modulus`:RSA 模数 N,384 字节无符号大端整数的无填充 base64url,字符串长度 512。
- `base`:起始值,编码规则与 modulus 相同。
- `iterations`:服务端给出的最终迭代数,已经包含文件大小分档和风控倍率。
- `challenge_id` 和 `asset_id` 必须原样用于后续授权请求。
## 顺序算法
```text
N = decode_base64url_unsigned_big_endian_384(modulus)
y = decode_base64url_unsigned_big_endian_384(base)
repeat exactly iterations times:
y = (y * y) mod N
solution_bytes = unsigned_big_endian(y, exactly 384 bytes, left padded with zeroes)
solution = base64url_without_padding(solution_bytes)
```
最终值在数学上是 `base^(2^iterations) mod N`。但客户端不知道 RSA 模数的陷门,无法化简指数;每轮又依赖前一轮结果,因此不能把迭代区间拆给多个线程后再合并。正确实现是一条顺序模平方循环。
## 编码边界
1. modulus、base、solution 在线上都固定为 384 字节、512 个 base64url 字符。
2. 整数按无符号大端解释,不能使用十进制、十六进制或可变长字节串。
3. solution 前导零必须保留到 384 字节。
4. base64url 使用 `-` 和 `_`,不得包含 `=` padding。
5. solution 必须满足 `0 < solution < N`。
## 授权请求
```json
{
"challenge_id": "挑战响应中的 challenge_id",
"asset_id": "创建挑战时使用的 asset_id",
"solution": "512 字符定长 base64url 结果"
}
```
挑战会绑定 API V2、算法、资产、客户端网络前缀和有效期,并且成功授权后只能消费一次。不要跨资产、跨 Web/API 或跨 V1/V2 复用挑战;挑战过期或失败后应重新创建。
## 服务端校验原理
主节点持有仅存在于内存中的 RSA 陷门,可以快速计算相同最终值。创建挑战时保存的是定长预期结果的摘要;授权时先检查挑战绑定关系和生命周期,再检查 solution 的定长编码、数值范围和摘要。RSA 陷门、预期答案和 solution 都不会写入独立 PoW 遥测日志。
Step 5: submit the solution and obtain authorization
/api/public/v2/api/authorizationsSubmit the sequential-work result to obtain short-lived authorization bound to one node. challenge_id, asset_id and solution must all belong to the same V2 challenge flow. The programmatic API currently has no telemetry fields that clients need to report.
| Parameter | Type | Description |
|---|---|---|
| challenge_id | JSON | Challenge identifier |
| asset_id | JSON | Asset to download |
| solution | JSON | Fixed 512-character base64url repeated-squaring result |
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": "由服务端返回的实际下载节点 URL",
"download_token": "43 字符短时随机令牌",
"expires_at": "2026-09-03T12:05:00Z",
"range_concurrency_limit": 32,
"max_bytes": 20971520
}
}
Step 6: request the download node
Access download_url directly and send download_token in the request header. It normally points to a download node. After authorization succeeds, the actual file request may use a different egress from the challenge stage, but the token is a Bearer credential and can be used by whoever possesses it. Also honor range_concurrency_limit and max_bytes from the response.
{download_url}Example Request
GET {download_url}
Authorization: Bearer <download_token>
For resumable downloads, use a single Range segment:
GET {download_url}
Authorization: Bearer <download_token>
Range: bytes=1048576-2097151
Other interfaces
These interfaces are not steps in the programmatic download flow; they are for subscriptions, status queries and troubleshooting.
Blocklist subscription
/api/public/v1/blocklist.txt/api/public/v1/blocklist.jsonReturns the active public download blocklist. It includes the static quota.yaml blocklist and manual or automatic local blocks, excludes remote subscription snapshots, and is cached by the server for 60 seconds.
TXT Example Response
# [枫源镜像封禁] 封禁原因: traffic_limit_exceeded, 来源: local_auto_ban, 封禁后尝试次数: 3
192.0.2.123
JSON Example Response
{
"status": "success",
"data": {
"blocks": [
{"entry": "192.0.2.123", "reason": "traffic_limit_exceeded", "attempts_after_block": 3, "blocked_at": "2026-06-21T12:00:00Z"}
]
}
}
Project developer update trigger
/api/developer/v1/projects/{project_id}/syncLets a mirrored project owner trigger an immediate update check after publishing a GitHub Release. Each Developer API Token is bound to exactly one project and can be generated or reset from that project's card in the admin panel.
| Parameter | Type | Description |
|---|---|---|
| project_id | Path | Project identifier |
| Authorization | Header | Bearer project Developer API Token |
Example Request
POST /api/developer/v1/projects/example/sync
Authorization: Bearer <project_token>
Each project allows 100 effective triggers per day by default. Responses include X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset; exhausted quota returns 429.
Accepted requests return 202. If the same project is already being scanned, the API still returns 202 but does not start another scan or consume another daily request.
Example Response
{
"status": "success",
"message": "更新检测任务已接受",
"data": {"project_id": "example", "accepted": true, "already_running": false}
}
Common status codes: 401 for missing or invalid tokens; 403 for a token/project mismatch; 404 for an unknown project; 409 for a disabled project; and 429 for exhausted daily quota or excessive authentication failures.
Changelog
/api/public/v1/changelogQuery the site's changelog in reverse chronological order. minimum_level accepts info, notice, warn or critical; q searches titles and visible descriptions; limit defaults to 20 and is capped at 50. When another page exists, the response returns an opaque next_cursor.
Example Request
GET /api/public/v1/changelog?minimum_level=notice&q=下载&limit=20
/api/public/v2/authorizations/{authorization_id}Use the corresponding download_token to query V2 authorization state, expiry, public node name and accounted bytes sent. The old /api/public/v1/authorizations/{authorization_id} path is retained only as a compatibility alias; new clients should not use it.
| Parameter | Type | Description |
|---|---|---|
| authorization_id | Path | Authorization identifier |
| Authorization | Header | Bearer <download_token> |
node_name is the new explicit field. node_id is temporarily retained for backward compatibility and currently contains the same public node name; it does not expose the internal node ID. New clients should read node_name.
Example Request
GET /api/public/v2/authorizations/auth_123
Authorization: Bearer <download_token>
Example Response
{
"status": "success",
"data": {
"authorization_id": "auth_123",
"asset_id": "asset_123",
"node_name": "public-node-name",
"node_id": "public-node-name",
"state": "issued",
"expires_at": "2026-09-03T12:05:00Z",
"bytes_accounting_enabled": true,
"sent_bytes": 1048576,
"first_transfer_at": "2026-09-03T12:01:10Z"
}
}