# FS 使用文档与 API

服务入口：https://fs.disk.zone · disk.zone 的 FS 文件存储

FS 以根目录组织文件。上传之后，文件分发在 Cloudflare 全球的静态资源 CDN 上；你按路径管理它们，用 key 分享它们。账号就是 disk.zone 账号，和 disk.zone 的所有项目共用登录和 API Key。

- [个人空间](https://fs.disk.zone/app)：登录后新建根目录，上传、浏览、分享和删除文件
- [API 索引](https://fs.disk.zone/api) · [OpenAPI](https://fs.disk.zone/api/openapi.json) · [llms.txt](https://fs.disk.zone/llms.txt)
- [disk.zone 开发者文档](https://disk.zone/developers)：登录、API Key、统一响应格式

## 快速开始

1. 打开 [个人空间](https://fs.disk.zone/app)，用 disk.zone 账号登录，新建一个根目录，例如 `photos`。
2. 在 [disk.zone 账户页](https://disk.zone/app/account) 创建一把账户 API Key（`dzk.…`）。
3. 上传、下载：

```bash
DZ_KEY='dzk.…'
curl -T a.jpg "https://fs.disk.zone/v1/photos/2026/a.jpg" -H "Authorization: Bearer $DZ_KEY"
# → {"ok":true,"data":{"path":"2026/a.jpg","key":"…","size":12345,"readyAt":…,"url":"https://fs.disk.zone/f/…"}}
curl -o a.jpg "https://fs.disk.zone/v1/photos/2026/a.jpg" -H "Authorization: Bearer $DZ_KEY"
```

## 凭据

| 凭据 | 形式 | 用在哪 |
| --- | --- | --- |
| 账户 API Key | `dzk.<用户名>.<keyid>.<secret>`，在 [账户页](https://disk.zone/app/account) 创建 | 脚本、服务端、AI：`Authorization: Bearer dzk.…` |
| 访问令牌 | 登录 disk.zone 时 `"mode":"token"` 得到 | `Authorization: Bearer <access_token>` |
| 网页登录 | disk.zone 的登录 Cookie（*.disk.zone 共享） | 个人空间网页 |
| 文件 key | 上传时返回 | `/f/{key}` 公开下载，不需要任何凭据 |

只有根目录的主人能上传、列出、删除其中的文件；别人的根目录返回 404。

## 根目录

根目录名：1–48 个小写字母、数字、`_` 或 `-`，以字母或数字开头。名字全站唯一，先到先得。

| 方法 | 地址 | 结果 |
| --- | --- | --- |
| GET | `/api/roots` | 自己的根目录：`{items: [{name, created}]}` |
| POST | `/api/roots`，body `{"name":"photos"}` | 新建，`201 {name, created}`；名字已被占用 409 |
| GET | `/api/roots/{root}` | `{name, created}` |
| DELETE | `/api/roots/{root}` | 删除；根目录里还有文件时返回 409 `root_not_empty` |

## 上传

```bash
curl -T a.jpg "https://fs.disk.zone/v1/photos/2026/a.jpg" -H "Authorization: Bearer $DZ_KEY"
```

- `PUT`（或 `POST`）`/v1/{root}/{path}`，请求体就是文件。同一路径再传一次就是替换。
- 文件类型取 `?type=`，没有时用请求的 `Content-Type`。
- 一次请求最多约 100 MB。更大的文件分几次 `PUT` 同一个地址，每次带 `?upload=<自定的 32 位十六进制 id>&offset=<这一段的起点>&total=<总长>`，最后一段返回完整的结果。
- 返回 `{path, key, size, readyAt, url}`。`readyAt`（Unix 毫秒）之后文件在 CDN 上可读；`url` 是公开的下载地址。

路径的每一段不能为空，不能是 `.` 或 `..`，分别 URL 编码。

## 下载与分享

| 方法 | 地址 | 说明 |
| --- | --- | --- |
| GET / HEAD | `/v1/{root}/{path}` | 按路径下载，需要登录或账户 Key；支持 Range |
| GET | `/v1/{root}/{path}?meta` | 文件记录 `{path, key, size, type, at}` |
| GET | `/f/{key}` | 按 key 下载，**不需要登录**；支持 Range |

刚上传的文件在分发到 CDN 前返回 `425 not_ready` 和 `retry-after`，稍等再试即可。

key 是文件的永久下载凭据：拿到 key 的人都能下载这个文件，像对待公开链接一样对待它。多个 key 用 `.` 连起来（`/f/k1.k2.k3`），一次下载它们按顺序拼在一起的内容。

## 列出与删除

```bash
curl "https://fs.disk.zone/v1/photos/2026/?limit=100" -H "Authorization: Bearer $DZ_KEY"
# → {"ok":true,"data":{"files":[{"path":"2026/a.jpg","size":12345,"type":"image/jpeg","at":…}],"next":null}}
curl -X DELETE "https://fs.disk.zone/v1/photos/2026/a.jpg" -H "Authorization: Bearer $DZ_KEY"
```

- 以 `/` 结尾的路径列出该目录下（含子目录）的文件；`/v1/{root}/` 列出整个根目录。
- 每页最多 1000 个，默认 100；还有更多时返回 `next`，把它放进 `?after=` 取下一页。
- 删除一个路径之后，这个路径就不在了；已经分享出去的 key 会在一段时间后失效。

## 响应格式与错误

除了下载（直接返回文件），所有接口都返回 disk.zone 统一的格式：

```json
{"ok":true,"data":…}
{"ok":false,"error":{"code":"snake_case","message":"中文说明"}}
```

| 状态 | 常见 code | 含义 |
| --- | --- | --- |
| 400 | `bad_request`、`invalid_name` | 参数、路径或根目录名不合法 |
| 401 | `unauthorized` | 没有登录或凭据无效 |
| 404 | `not_found` | 根目录或文件不存在，或不属于你 |
| 409 | `name_taken`、`root_not_empty` | 名字已被占用，或根目录不空 |
| 413 | `storage_error` | 文件太大，改用分段上传 |
| 425 | `not_ready` | 文件还在分发到 CDN，按 `retry-after` 稍后再试 |
| 502 | `storage_error` | 存储暂时出错，可以重试 |

## 它是怎么工作的

FS 是 disk.zone 的一个子项目：文件由 Cloudflare Workers 静态资源组成的文件池保存和分发；小文件在推上 CDN 前，先暂存在 [KV](https://kv.disk.zone)（disk.zone 的键值存储）里；KV 的存储桶配置放在 [SKV](https://skv.disk.zone) 里。所以 FS 依赖 KV，KV 依赖 SKV。

## 给 AI 的说明

把这份文档（`https://fs.disk.zone/docs.md`）和一把账户 API Key 交给 AI，它就能管理你的根目录和文件。先调用 `GET /api/me` 确认身份。

- 上传是 `PUT /v1/{root}/{path}`，下载是 `GET` 同一个地址
- 所有 JSON 接口都返回 `{"ok":…}`，以 `error.code` 判断错误；`425` 表示稍后再读
- 文件 key 是公开凭据，分享前想清楚
- 不要把账户 Key 写进公开的代码、日志或网页
