# KV 使用文档与 API

服务入口：https://kv.disk.zone · disk.zone 的 KV 键值存储

KV 把数据放在按命名空间组织的键值存储里：值存放在 B2 存储池，带命名空间 Key 的单个键读写在离你最近的边缘完成。账号就是 disk.zone 账号，和 disk.zone 的所有项目共用登录和 API Key。

- [个人空间](https://kv.disk.zone/app)：登录后新建、管理命名空间，浏览数据
- [API 索引](https://kv.disk.zone/api) · [OpenAPI](https://kv.disk.zone/api/openapi.json) · [llms.txt](https://kv.disk.zone/llms.txt)
- [disk.zone 开发者文档](https://disk.zone/developers)：登录、API Key、统一响应格式

## 快速开始

1. 打开 [个人空间](https://kv.disk.zone/app)，用 disk.zone 账号登录。
2. 新建一个命名空间，例如 `my-app`。页面会显示它的 Key（`kvk_…`），以后也随时可以在控制台查看、复制。
3. 用这把 Key 读写：

```bash
KEY='kvk_…'
curl -X PUT "https://kv.disk.zone/v1/my-app/greeting" -H "Authorization: Bearer $KEY" --data-binary "hello"
curl "https://kv.disk.zone/v1/my-app/greeting" -H "Authorization: Bearer $KEY"
# → hello
```

## 三种凭据

| 凭据 | 形式 | 能做什么 |
| --- | --- | --- |
| 命名空间 Key | `kvk_…`，新建或更换命名空间时返回 | 读写这一个命名空间的数据。最快：单个键的请求在边缘直接完成 |
| 账户 API Key | `dzk.<用户名>.<keyid>.<secret>`，在 [disk.zone 账户页](https://disk.zone/app/account) 创建 | 管理名下全部命名空间，也能读写它们的数据 |
| 网页登录 | disk.zone 的登录 Cookie（*.disk.zone 共享） | 个人空间网页里的一切操作 |

请求里怎么带：

- `Authorization: Bearer <凭据>`（三种都可以）
- 命名空间 Key 也可以放在 `?apikey=` 或 `x-api-key` 头里，方便浏览器和简单脚本

命名空间 Key 只能操作自己的命名空间；账户 Key 和登录只能操作自己名下的命名空间。命名空间不存在和 Key 不对返回同样的 401，不会泄露哪些名字已被占用。

## 命名空间

命名空间名：1–64 个字母、数字、`_` 或 `-`，以字母或数字开头。名字全站唯一，先到先得。

| 方法 | 地址 | 结果 |
| --- | --- | --- |
| GET | `/api/namespaces` | 自己的命名空间：`{items: [{name, state, created}]}` |
| POST | `/api/namespaces`，body `{"name":"my-app"}` | 新建，`201 {name, key, created}`；名字已被占用 409 |
| GET | `/api/namespaces/{ns}` | `{name, state, created}` |
| POST | `/api/namespaces/{ns}/key` | 换一把新 Key，返回 `{name, key}`；旧 Key 一分钟内失效 |
| DELETE | `/api/namespaces/{ns}` | 删除：Key 立即失效，数据在后台清空，清空后名字才能再用 |

这些接口用账户 API Key、访问令牌或网页登录调用：

```bash
curl -X POST "https://kv.disk.zone/api/namespaces" -H "Authorization: Bearer $DZ_KEY" \
  -H "Content-Type: application/json" --data '{"name":"my-app"}'
# → {"ok":true,"data":{"name":"my-app","key":"kvk_…","created":1793900000000}}
```

命名空间 Key 随时可以查看：控制台里点开命名空间就能看到，接口 `GET /api/namespaces` 和 `GET /api/namespaces/{ns}` 也会返回 `key`（只有所有者能看到）。换一把 Key（`POST /api/namespaces/{ns}/key`）后旧 Key 在一分钟内失效。

## 读写单个键

| 方法 | 地址 | 说明 |
| --- | --- | --- |
| PUT | `/v1/{ns}/{key}` | 请求体就是值，任意字节；再写就是覆盖。返回 `{etag, expires}` |
| GET | `/v1/{ns}/{key}` | 返回值本身，不加任何包装；不存在 404 |
| HEAD | `/v1/{ns}/{key}` | 只要头部：大小、etag、到期时间 |
| DELETE | `/v1/{ns}/{key}` | 删除这个键的全部版本；不存在也算成功 |

键可以包含 `/`，例如 `users/alice/profile.json`；每一段分别 URL 编码。键不能有 `.` 或 `..` 段，不能有控制字符，整个对象名不超过 1024 字节。

读取的响应头：

- `etag`：版本号，写入时返回的 `etag` 就是它
- `content-type`：写入时的 Content-Type
- `x-kv-metadata`：写入时附带的元数据（URL 编码的 JSON）
- `x-kv-expiration`：到期时刻（Unix 秒），没有到期时间则没有这个头

## 过期、元数据与条件请求

写入时可以带：

- `?ttl=3600`：多少秒后过期（也接受 `?expiration_ttl=`）
- `?expiration=1793900000`：到期时刻（Unix 秒）
- `?metadata={"v":1}` 或请求头 `x-kv-metadata`（URL 编码的 JSON）

到期的键读起来就像不存在，并在读到时被删除。

读取支持 `Range: bytes=…`（单个区间，返回 206）、`If-Range`、`If-Match`、`If-Unmodified-Since`。

```bash
curl -X PUT "https://kv.disk.zone/v1/my-app/session/42?ttl=600" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" --data '{"user":"alice"}'
curl -r 0-99 "https://kv.disk.zone/v1/my-app/big.bin" -H "Authorization: Bearer $KEY" -o head.bin
```

## 列出、一次读多个、批量删除

```bash
curl "https://kv.disk.zone/v1/my-app?prefix=users/&limit=1000" -H "Authorization: Bearer $KEY"
# → {"ok":true,"data":{"keys":[{"name":"users/alice","size":42,"modified":"…","id":"…","expires":null}],"cursor":"…","list_complete":false}}
```

| 参数 | 说明 |
| --- | --- |
| `prefix` | 只列出以它开头的键 |
| `limit` | 每页最多多少个，默认 1000 |
| `cursor` | 上一页返回的 cursor，原样带回 |
| `after` / `before` | 只列出名字在这两者之间的（都不含） |

键按 UTF-8 字节顺序返回。一页可能少于 `limit`，是否列完以 `list_complete` 为准。`id` 是版本号，批量删除时带上可以更快。

一次读多个键（最多 1000 个），按请求的顺序返回：

```bash
curl -X POST "https://kv.disk.zone/v1/my-app?get" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" --data '{"keys":["a","b","nope"],"ranges":[null,[0,100],null]}'
```

返回体里每个键先是一行 JSON，紧接着正好 `size` 个字节，然后是下一个键：

```
{"key":"a","status":200,"size":5,"etag":"…","type":"text/plain"}
hello{"key":"b","status":206,"size":100,"etag":"…"}
<100 个字节>{"key":"nope","status":404,"size":0}
```

| status | 含义 |
| --- | --- |
| 200 | 整个值 |
| 206 | 请求的区间（区间超出末尾时字节会少一些，以 `size` 为准） |
| 404 | 没有这个键 |
| 416 | 区间的起点已经超出末尾 |
| 502 | 读这个键时存储出错，可以单独再读一次 |

`ranges` 可选，和 `keys` 一一对应：`[起点, 字节数]`，或 `null` 表示整个值；字节数为 0 时只确认键存在。行里还可能有 `expires`（毫秒）和 `metadata`。

批量删除一次最多 1000 个：

```bash
curl -X POST "https://kv.disk.zone/v1/my-app?delete" -H "Authorization: Bearer $KEY" \
  -H "Content-Type: application/json" --data '{"keys":["a","b"],"ids":["<id of a>",null]}'
# → {"ok":true,"data":{"deleted":2,"failed":[]}}
```

## 响应格式与错误

除了读取（直接返回值），所有接口都返回 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`、`name_not_free` | 名字已被占用，或删除后还在清理 |
| 502 | 存储上游的 code | 存储暂时出错，可以重试 |
| 503 | `storage_unavailable` 等 | 暂时不可用，按 `retry-after` 重试 |

响应头 `x-kv-via` 说明这次请求由边缘入口（`snippet`）还是后端（`worker`）完成，排查时有用。

## 限制

| 项目 | 限制 |
| --- | --- |
| 单个值 | 约 100 MB（一次请求的上限） |
| 键 | 整个对象名不超过 1024 字节，不能有 `.` / `..` 段和控制字符 |
| 命名空间名 | 1–64 个字母、数字、`_`、`-` |
| 一次读多个 | 一次 1–1000 个 |
| 批量删除 | 一次 1–1000 个 |
| 列出 | 每页最多 1 万个 × 存储桶数 |
| 更换 Key | 新 Key 几秒内各地可用；旧 Key 最多一分钟后失效 |
| 删除命名空间 | Key 最多一分钟后各地失效 |

## 给 AI 的说明

把这份文档（`https://kv.disk.zone/docs.md`）和一把命名空间 Key 交给 AI，它就可以直接读写这个命名空间。要让 AI 新建或管理命名空间，给它账户 API Key（`dzk.…`），并让它先调用 `GET /api/me` 确认身份。

- 读写用 `/v1/{ns}/{key}`，读取得到的就是值本身
- 其他接口都返回 `{"ok":…}`，以 `error.code` 判断错误
- 忘了 Key 就用 `GET /api/namespaces/{ns}` 取回
- 不要把 Key 写进公开的代码、日志或网页
