# 文档、讨论和文件 API

> 文档、公告和讨论、Drive 里的文件和文件夹，以及正文里的附件。

原文：https://qunli.co/docs/developers/api/documents

由群历 REST API 的 OpenAPI 契约（`openapi/qunli-api.yaml`，qunli-api 提交 `f18e40d`）生成。路径都相对于 `https://qunli.co`；认证见[认证](https://qunli.co/docs/developers/authentication)，响应包装、分页和错误格式见[约定](https://qunli.co/docs/developers/conventions)。字段说明是契约里的英文原文。

## 文档

### `GET /api/v1/accounts/{account_id}/documents`

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `content` | 查询 | `full` \| `summary` | 否 | full (default): rows carry their whole body. summary: rows leave out content_html, content_json, content_markdown and content_version and carry only preview_text / excerpt (documents: also preview_html); read one record for its body. Other values → 400 param_invalid. 默认 `full`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Document list | `data`: `Recording`[] |
| `401` | Missing or invalid authentication. | `ProblemDetails` |

### `POST /api/v1/accounts/{account_id}/documents`

Create a document (account-scoped; project_id in the body)

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |

请求体（`application/json`，必填）：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `project_id` | string (uuid) | 否 | createAccountDocument only — the project to create the document in. |
| `recording` | object | 否 |  |
| `recording.id` | string (uuid) | 否 |  |
| `recording.title` | string | 否 |  |
| `recording.status` | string | 否 |  |
| `recording.content_html` | string \| null | 否 | 已弃用。 |
| `recording.content_json` | any | 否 | 已弃用。 |
| `recording.content_markdown` | string \| null | 否 | Canonical body (GFM). When present the server renders and sanitizes content_html from it, ignoring any client-sent content_html/content_json. Preferred over content_html and content_json for new integrations. Hard cap 131072 bytes (128KB, UTF-8 bytes — not characters); a larger body is rejected with 422 validation_error ("正文超出 128KB 上限"): upload long content as an attachment or split it into multiple documents. Pre-existing over-limit rows stay readable and editable as long as the body is untouched. |
| `recording.content_version` | integer \| null | 否 | Update only. The content_version the edit started from (recommended for every body edit). When present and the request changes the body, a stale value is refused with 409 content_conflict and nothing is written; the problem body carries the current content_version. Omit it (or send null) for last-write-wins. Title- or metadata-only updates never conflict. |
| `recording.parent_id` | string \| null | 否 | Drive folder ref ("B12") or uuid to create/keep the document in. |
| `document` | object | 否 |  |
| `document.id` | string (uuid) | 否 |  |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `201` | Created document | `data`: `Recording` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `403` | Authenticated but not permitted. | `ProblemDetails` |
| `422` | Validation failed. | `ProblemDetails` |

### `GET /api/v1/accounts/{account_id}/documents/{id}`

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Document | `data`: `Recording` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `PATCH /api/v1/accounts/{account_id}/documents/{id}`

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

请求体（`application/json`，必填）：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `project_id` | string (uuid) | 否 | createAccountDocument only — the project to create the document in. |
| `recording` | object | 否 |  |
| `recording.id` | string (uuid) | 否 |  |
| `recording.title` | string | 否 |  |
| `recording.status` | string | 否 |  |
| `recording.content_html` | string \| null | 否 | 已弃用。 |
| `recording.content_json` | any | 否 | 已弃用。 |
| `recording.content_markdown` | string \| null | 否 | Canonical body (GFM). When present the server renders and sanitizes content_html from it, ignoring any client-sent content_html/content_json. Preferred over content_html and content_json for new integrations. Hard cap 131072 bytes (128KB, UTF-8 bytes — not characters); a larger body is rejected with 422 validation_error ("正文超出 128KB 上限"): upload long content as an attachment or split it into multiple documents. Pre-existing over-limit rows stay readable and editable as long as the body is untouched. |
| `recording.content_version` | integer \| null | 否 | Update only. The content_version the edit started from (recommended for every body edit). When present and the request changes the body, a stale value is refused with 409 content_conflict and nothing is written; the problem body carries the current content_version. Omit it (or send null) for last-write-wins. Title- or metadata-only updates never conflict. |
| `recording.parent_id` | string \| null | 否 | Drive folder ref ("B12") or uuid to create/keep the document in. |
| `document` | object | 否 |  |
| `document.id` | string (uuid) | 否 |  |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Updated document | `data`: `Recording` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `403` | Authenticated but not permitted. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |
| `409` | content_conflict — the body was saved elsewhere since the request's recording.content_version; nothing was written. Rebase the edit onto the returned content_version (or reload) and save again. | `ProblemDetails` |
| `422` | Validation failed. | `ProblemDetails` |

### `DELETE /api/v1/accounts/{account_id}/documents/{id}`

Soft-delete a document (returns the trashed recording)

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Trashed document | `data`: `Recording` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `403` | Authenticated but not permitted. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `GET /api/v1/accounts/{account_id}/documents/{id}/publication_recipients`

Preview who will be notified when the document is published

Includes publication notifications and pending mentions as if the caller published it now, excluding the author, the caller, people without access, and (when the caller is an agent) other agents who would only get a publication notice or whose mention the caller's remaining hourly agent → agent allowance does not cover (mentions take the remaining slots in body order). The list is refreshed at publication time. Does not send notifications or spend the allowance.

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Current publication recipients | `data`: `UserRef`[] |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `GET /api/v1/accounts/{account_id}/projects/{project_id}/documents`

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `project_id` | 路径 | string | 是 |  |
| `content` | 查询 | `full` \| `summary` | 否 | full (default): rows carry their whole body. summary: rows leave out content_html, content_json, content_markdown and content_version and carry only preview_text / excerpt (documents: also preview_html); read one record for its body. Other values → 400 param_invalid. 默认 `full`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Document list | `data`: `Recording`[] |
| `401` | Missing or invalid authentication. | `ProblemDetails` |

### `POST /api/v1/accounts/{account_id}/projects/{project_id}/documents`

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `project_id` | 路径 | string | 是 |  |

请求体（`application/json`，必填）：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `project_id` | string (uuid) | 否 | createAccountDocument only — the project to create the document in. |
| `recording` | object | 否 |  |
| `recording.id` | string (uuid) | 否 |  |
| `recording.title` | string | 否 |  |
| `recording.status` | string | 否 |  |
| `recording.content_html` | string \| null | 否 | 已弃用。 |
| `recording.content_json` | any | 否 | 已弃用。 |
| `recording.content_markdown` | string \| null | 否 | Canonical body (GFM). When present the server renders and sanitizes content_html from it, ignoring any client-sent content_html/content_json. Preferred over content_html and content_json for new integrations. Hard cap 131072 bytes (128KB, UTF-8 bytes — not characters); a larger body is rejected with 422 validation_error ("正文超出 128KB 上限"): upload long content as an attachment or split it into multiple documents. Pre-existing over-limit rows stay readable and editable as long as the body is untouched. |
| `recording.content_version` | integer \| null | 否 | Update only. The content_version the edit started from (recommended for every body edit). When present and the request changes the body, a stale value is refused with 409 content_conflict and nothing is written; the problem body carries the current content_version. Omit it (or send null) for last-write-wins. Title- or metadata-only updates never conflict. |
| `recording.parent_id` | string \| null | 否 | Drive folder ref ("B12") or uuid to create/keep the document in. |
| `document` | object | 否 |  |
| `document.id` | string (uuid) | 否 |  |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `201` | Created document | `data`: `Recording` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `403` | Authenticated but not permitted. | `ProblemDetails` |
| `422` | Validation failed. | `ProblemDetails` |

## 讨论

### `GET /api/v1/accounts/{account_id}/messages`

List message recordings across the account

Active messages in accessible projects, newest first (created_at DESC, id DESC). Follow meta.has_next with page + 1 to read all messages.

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `content` | 查询 | `full` \| `summary` | 否 | full (default): rows carry their whole body. summary: rows leave out content_html, content_json, content_markdown and content_version and carry only preview_text / excerpt (documents: also preview_html); read one record for its body. Other values → 400 param_invalid. 默认 `full`。 |
| `page` | 查询 | integer | 否 | 默认 `1`。 |
| `limit` | 查询 | integer | 否 | Clamped to 1–100. 默认 `30`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Message list | `data`: `Recording`[] · `meta`: `PaginationMeta` |
| `400` | Malformed request or invalid parameter. | `ProblemDetails` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |

### `GET /api/v1/accounts/{account_id}/messages/{id}`

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Message | `data`: `Recording` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `PATCH /api/v1/accounts/{account_id}/messages/{id}`

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

请求体（`application/json`，必填）：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `recording` | `RecordingFields` | 否 | The fields every recording type shares, sent under `recording`. `id` is create-only (a client-generated UUID). |
| `recording.id` | string (uuid) | 否 |  |
| `recording.title` | string | 否 |  |
| `recording.status` | string | 否 |  |
| `recording.content_html` | string \| null | 否 | 已弃用。 |
| `recording.content_json` | any | 否 | 已弃用。 |
| `recording.content_markdown` | string \| null | 否 | Canonical body (GFM). When present the server renders and sanitizes content_html from it, ignoring any client-sent content_html/content_json. Preferred over content_html and content_json for new integrations. Hard cap 131072 bytes (128KB, UTF-8 bytes — not characters); a larger body is rejected with 422 validation_error ("正文超出 128KB 上限"): upload long content as an attachment or split it into multiple documents. Pre-existing over-limit rows stay readable and editable as long as the body is untouched. |
| `recording.content_version` | integer \| null | 否 | Update only. The content_version the edit started from (recommended for every body edit). When present and the request changes the body, a stale value is refused with 409 content_conflict and nothing is written; the problem body carries the current content_version. Omit it (or send null) for last-write-wins. Title- or metadata-only updates never conflict. |
| `message` | object | 否 |  |
| `message.id` | string (uuid) | 否 |  |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Updated message | `data`: `Recording` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `403` | Authenticated but not permitted. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |
| `409` | content_conflict — the body was saved elsewhere since the request's recording.content_version; nothing was written. Rebase the edit onto the returned content_version (or reload) and save again. | `ProblemDetails` |
| `422` | Validation failed. | `ProblemDetails` |

### `DELETE /api/v1/accounts/{account_id}/messages/{id}`

Soft-delete a message (returns the trashed recording)

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Trashed message | `data`: `Recording` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `403` | Authenticated but not permitted. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `GET /api/v1/accounts/{account_id}/projects/{project_id}/messages`

Active messages, newest first (created_at DESC, id DESC). Returns pagination meta; follow meta.has_next with page + 1 for older messages.

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `project_id` | 路径 | string | 是 |  |
| `content` | 查询 | `full` \| `summary` | 否 | full (default): rows carry their whole body. summary: rows leave out content_html, content_json, content_markdown and content_version and carry only preview_text / excerpt (documents: also preview_html); read one record for its body. Other values → 400 param_invalid. 默认 `full`。 |
| `page` | 查询 | integer | 否 | 默认 `1`。 |
| `limit` | 查询 | integer | 否 | Clamped to 1–100. 默认 `30`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Message list | `data`: `Recording`[] · `meta`: `PaginationMeta` |
| `400` | Malformed request or invalid parameter. | `ProblemDetails` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |

### `POST /api/v1/accounts/{account_id}/projects/{project_id}/messages`

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `project_id` | 路径 | string | 是 |  |

请求体（`application/json`，必填）：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `recording` | `RecordingFields` | 否 | The fields every recording type shares, sent under `recording`. `id` is create-only (a client-generated UUID). |
| `recording.id` | string (uuid) | 否 |  |
| `recording.title` | string | 否 |  |
| `recording.status` | string | 否 |  |
| `recording.content_html` | string \| null | 否 | 已弃用。 |
| `recording.content_json` | any | 否 | 已弃用。 |
| `recording.content_markdown` | string \| null | 否 | Canonical body (GFM). When present the server renders and sanitizes content_html from it, ignoring any client-sent content_html/content_json. Preferred over content_html and content_json for new integrations. Hard cap 131072 bytes (128KB, UTF-8 bytes — not characters); a larger body is rejected with 422 validation_error ("正文超出 128KB 上限"): upload long content as an attachment or split it into multiple documents. Pre-existing over-limit rows stay readable and editable as long as the body is untouched. |
| `recording.content_version` | integer \| null | 否 | Update only. The content_version the edit started from (recommended for every body edit). When present and the request changes the body, a stale value is refused with 409 content_conflict and nothing is written; the problem body carries the current content_version. Omit it (or send null) for last-write-wins. Title- or metadata-only updates never conflict. |
| `message` | object | 否 |  |
| `message.id` | string (uuid) | 否 |  |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `201` | Created message | `data`: `Recording` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `403` | Authenticated but not permitted. | `ProblemDetails` |
| `422` | Validation failed. | `ProblemDetails` |

## 文件和文件夹

### `GET /api/v1/accounts/{account_id}/projects/{project_id}/drive_items`

Lists one folder level (parent_id, blank = project root) or the whole tree (recursive=true). Folders always come first, then the requested sort. Every item carries its full fields — the same shape as the single-item endpoints (documents include their body, uploads their file URLs, folders their children_count). Drafts and trashed items are omitted.

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `project_id` | 路径 | string | 是 |  |
| `content` | 查询 | `full` \| `summary` | 否 | full (default): rows carry their whole body. summary: rows leave out content_html, content_json, content_markdown and content_version and carry only preview_text / excerpt (documents: also preview_html); read one record for its body. Other values → 400 param_invalid. 默认 `full`。 |
| `parent_id` | 查询 | string | 否 | Folder ref ("B12") or recording uuid; omit for the project root. |
| `recursive` | 查询 | boolean | 否 | List every level of the project's drive (parent_id is ignored). |
| `type` | 查询 | string | 否 | Comma-separated recordable types to include (Document, Upload, Folder). |
| `sort` | 查询 | `name` \| `updated_at` \| `created_at` \| `byte_size` \| `kind` | 否 | 默认 `updated_at`。 |
| `direction` | 查询 | `asc` \| `desc` | 否 | Defaults to asc for name/kind, desc otherwise. |
| `page` | 查询 | integer | 否 |  |
| `limit` | 查询 | integer | 否 | 默认 `100`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Drive items | `data`: `DriveItem`[] · `meta`: `PaginationMeta` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `POST /api/v1/accounts/{account_id}/projects/{project_id}/uploads`

Uploads a file into the project's drive. API clients send the file itself as multipart/form-data (upload[file], ≤100MB; bodies above that are refused with 413 payload_too_large before processing). The JSON form with a blob signed_id is the webapp's browser flow (session-only Active Storage direct upload, where regular files are capped at 100MB and videos at 500MB). recording[title] defaults to the filename; recording[parent_id] places the file in a folder.

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `project_id` | 路径 | string | 是 |  |

请求体（`multipart/form-data`、`application/json`，必填）：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `upload` | object | 是 |  |
| `upload.file` | file | 是 |  |
| `upload.thumbnail` | file | 否 |  |
| `recording` | object | 否 |  |
| `recording.title` | string | 否 |  |
| `recording.parent_id` | string \| null | 否 |  |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `201` | Created upload | `data`: `DriveItem` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `403` | Authenticated but not permitted. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |
| `413` | Request body exceeds the endpoint's size cap (code payload_too_large). | `ProblemDetails` |
| `422` | Validation failed. | `ProblemDetails` |

### `GET /api/v1/accounts/{account_id}/uploads/{id}`

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Upload | `data`: `DriveItem` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `PATCH /api/v1/accounts/{account_id}/uploads/{id}`

Renames the file (recording.title). The original filename is kept in `filename`.

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

请求体（`application/json`，必填）：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `recording` | object | 否 |  |
| `recording.id` | string (uuid) | 否 |  |
| `recording.title` | string | 否 | 1–255 characters after trimming (422 otherwise). |
| `recording.parent_id` | string \| null | 否 | Folder ref ("B12") or uuid. |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Updated upload | `data`: `DriveItem` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `403` | Authenticated but not permitted. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |
| `422` | Validation failed. | `ProblemDetails` |

### `DELETE /api/v1/accounts/{account_id}/uploads/{id}`

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Trashed upload | `data`: `DriveItem` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `403` | Authenticated but not permitted. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `GET /api/v1/accounts/{account_id}/uploads/{id}/download`

302 to a short-lived storage URL served as an attachment, named after the current title (original extension kept). Follow the redirect without forwarding the Authorization header.

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `302` | Redirect to the file bytes (a short-lived storage URL, or a same-origin proxy path). |  |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `GET /api/v1/accounts/{account_id}/uploads/{id}/raw`

302 to a short-lived inline storage URL (Range-capable; for img/video/audio elements).

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `302` | Redirect to the file bytes (a short-lived storage URL, or a same-origin proxy path). |  |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `GET /api/v1/accounts/{account_id}/uploads/{id}/content`

For pdf / text / spreadsheet uploads: 302 with a relative Location to the same-origin Active Storage proxy (always disposition=attachment, Range-capable) so browser previews can fetch() the bytes without bucket CORS. 404 for other kinds.

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `302` | Redirect to the file bytes (a short-lived storage URL, or a same-origin proxy path). |  |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `GET /api/v1/accounts/{account_id}/uploads/{id}/thumbnail`

302 to a ≤400px thumbnail (image variant, or the client-rendered video/PDF thumbnail); 404 when there is none.

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `302` | Redirect to the file bytes (a short-lived storage URL, or a same-origin proxy path). |  |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `GET /api/v1/accounts/{account_id}/uploads/{id}/preview`

Images only — 302 to a ≤2048px variant (the original when the image is too large to process).

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `302` | Redirect to the file bytes (a short-lived storage URL, or a same-origin proxy path). |  |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `POST /api/v1/accounts/{account_id}/projects/{project_id}/folders`

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `project_id` | 路径 | string | 是 |  |

请求体（`application/json`，必填）：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `recording` | object | 否 |  |
| `recording.id` | string (uuid) | 否 |  |
| `recording.title` | string | 否 | 1–255 characters after trimming (422 otherwise). |
| `recording.parent_id` | string \| null | 否 | Folder ref ("B12") or uuid. |
| `folder` | object | 否 |  |
| `folder.id` | string (uuid) | 否 |  |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `201` | Created folder | `data`: `DriveItem` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `403` | Authenticated but not permitted. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |
| `422` | Validation failed. | `ProblemDetails` |

### `GET /api/v1/accounts/{account_id}/folders/{id}`

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Folder (with its ancestors) | `data`: `DriveItem` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `PATCH /api/v1/accounts/{account_id}/folders/{id}`

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

请求体（`application/json`，必填）：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `recording` | object | 否 |  |
| `recording.id` | string (uuid) | 否 |  |
| `recording.title` | string | 否 | 1–255 characters after trimming (422 otherwise). |
| `recording.parent_id` | string \| null | 否 | Folder ref ("B12") or uuid. |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Updated folder | `data`: `DriveItem` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `403` | Authenticated but not permitted. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |
| `422` | Validation failed. | `ProblemDetails` |

### `DELETE /api/v1/accounts/{account_id}/folders/{id}`

Trashes the folder and everything below it.

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | Trashed folder | `data`: `DriveItem` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `403` | Authenticated but not permitted. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |

### `PATCH /api/v1/accounts/{account_id}/recordings/{recording_id}/parent`

Moves a document, upload or folder into a folder of the same project (parent.id = folder ref or uuid) or to the project root (parent.id = null). A folder cannot move into itself or its descendants (422).

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |
| `recording_id` | 路径 | string (ref) | 是 | 引用号，例如 `T1042`，大小写不敏感，可带前导 `#`。 |

请求体（`application/json`，必填）：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `parent` | object | 是 |  |
| `parent.id` | string \| null | 否 |  |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `200` | The moved item | `data`: `DriveItem` |
| `400` | Malformed request or invalid parameter. | `ProblemDetails` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `403` | Authenticated but not permitted. | `ProblemDetails` |
| `404` | Resource does not exist or has been trashed. | `ProblemDetails` |
| `422` | Validation failed. | `ProblemDetails` |

## 附件

### `POST /api/v1/accounts/{account_id}/attachments`

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `account_id` | 路径 | string | 是 | 团队的数字 id，和网页地址里的相同，例如 `2025`。 |

请求体（`multipart/form-data`、`application/json`，必填）：

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `attachment` | object | 是 |  |
| `attachment.file` | file | 是 |  |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `201` | Created attachment (access URLs and the embeddable card HTML) | `data`: `Attachment` |
| `401` | Missing or invalid authentication. | `ProblemDetails` |
| `422` | Validation failed. | `ProblemDetails` |

### `GET /api/v1/attachments/{id}`

Anyone holding the capability token can download this editor attachment.

认证：无需认证。

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `id` | 路径 | string | 是 | Attachment capability token |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `302` | Redirect to storage |  |
| `404` | Unknown capability token |  |

### `GET /api/v1/attachments/{id}/raw`

认证：无需认证。

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `id` | 路径 | string | 是 |  |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `302` | Redirect to raw file storage; requires the attachment capability token |  |
| `404` | Unknown capability token |  |

### `GET /api/v1/attachments/{id}/preview`

认证：无需认证。

参数：

| 参数 | 位置 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- | --- |
| `id` | 路径 | string | 是 |  |

响应：

| 状态 | 说明 | 返回 |
| --- | --- | --- |
| `302` | Redirect to the preview; requires the attachment capability token |  |
| `404` | Unknown capability token |  |

## 数据结构

上面的响应用到的对象。字段的类型是另一个对象时，它也列在这里。

### Recording

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | string (uuid) | 是 |  |
| `public_id` | string \| null | 否 |  |
| `legacy_id` | string \| null | 是 |  |
| `title` | string | 是 |  |
| `content_html` | string \| null | 否 |  |
| `content_json` | object \| null | 否 |  |
| `content_markdown` | string \| null | 否 | Canonical body (GFM). content_html is rendered server-side from it; null on rows not yet migrated from Tiptap JSON. |
| `content_version` | integer | 否 | Body version for compare-and-set saves. Starts at 0 and increases by one each time content_markdown / content_html / content_json actually changes (any write path). Send it back as recording.content_version when saving a body edit. |
| `preview_text` | string | 否 | Lists only. The body as plain text (HTML tags removed, entities left encoded, e.g. "&amp;"), cut to 80 UTF-16 units; "" without a body. |
| `excerpt` | string \| null | 否 | Lists only. Up to 100 characters of the body's text, images and videos shown as [图片] / [视频]; null without a body. |
| `preview_html` | string | 否 | Document lists only. The first 3000 characters of content_html (may end inside a tag), for thumbnails. |
| `comments_count` | integer | 是 |  |
| `type` | string | 是 |  |
| `status` | `draft` \| `active` \| `archived` \| `trashed` | 是 |  |
| `recordable_type` | string | 是 |  |
| `app_url` | string \| null | 是 |  |
| `account_id` | string (uuid) | 是 |  |
| `account_public_id` | string | 是 |  |
| `project_id` | string (uuid) | 是 |  |
| `project_public_id` | string | 是 |  |
| `creator_id` | string (uuid) | 是 |  |
| `parent_id` | string (uuid) \| null | 否 |  |
| `parent_public_id` | string \| null | 否 |  |
| `ancestors` | `FolderCrumb`[] | 否 |  |
| `created_at` | string (date-time) | 是 |  |
| `updated_at` | string (date-time) | 是 | Last change of the recording or of its body, whichever is later. |
| `reactions` | `Reaction`[] | 是 |  |
| `edited_by` | `UserRef` | 否 | Comments and discussions only: the last member other than the author who edited the title or body (the author's own later edits keep it); null when only the author edited it. |
| `trashed_by` | `UserRef` | 否 | Comments and discussions only: the member other than the author who trashed it; null otherwise (cleared by the author's own trash and on restore). A comment with status "trashed" and a trashed_by is a placeholder in GET .../recordings/:id/comments — its title and content are empty. |

### UserRef

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | string (uuid) | 是 | User UUID. |
| `public_id` | string \| null | 是 | Member ref ("U12") in this account. |
| `name` | string \| null | 是 |  |
| `avatar_url` | string \| null | 是 |  |
| `kind` | `human` \| `agent` \| null | 是 | null only for an unknown (deleted) user. |

### PaginationMeta

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `page` | integer | 是 |  |
| `limit` | integer | 是 |  |
| `total` | integer | 是 |  |
| `total_pages` | integer | 是 |  |
| `has_next` | boolean | 是 |  |

### DriveItem

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | string (uuid) | 是 |  |
| `public_id` | string \| null | 是 |  |
| `legacy_id` | string \| null | 是 |  |
| `title` | string | 是 |  |
| `type` | `Document` \| `Upload` \| `Folder` | 是 |  |
| `recordable_type` | `Document` \| `Upload` \| `Folder` | 是 |  |
| `recordable_id` | string (uuid) | 是 |  |
| `status` | `draft` \| `active` \| `archived` \| `trashed` | 是 |  |
| `app_url` | string \| null | 是 |  |
| `account_id` | string (uuid) | 是 |  |
| `account_public_id` | string | 是 |  |
| `project_id` | string (uuid) | 是 |  |
| `project_public_id` | string | 是 |  |
| `creator_id` | string (uuid) | 是 |  |
| `parent_id` | string (uuid) \| null | 是 | Recording id of the containing folder; null at the project root. |
| `parent_public_id` | string \| null | 是 |  |
| `ancestors` | `FolderCrumb`[] | 是 | Folder chain from the project root down to the parent. |
| `comments_count` | integer | 是 |  |
| `created_at` | string (date-time) | 是 |  |
| `updated_at` | string (date-time) | 是 |  |
| `reactions` | `Reaction`[] | 是 |  |
| `content_html` | string \| null | 否 |  |
| `content_json` | object \| null | 否 |  |
| `content_markdown` | string \| null | 否 |  |
| `content_version` | integer | 否 | Body version for compare-and-set saves. Starts at 0 and increases by one each time content_markdown / content_html / content_json actually changes (any write path). Send it back as recording.content_version when saving a body edit. |
| `preview_text` | string | 否 | Plain-text start of the body (80 UTF-16 units, entities encoded). |
| `excerpt` | string \| null | 否 | Up to 100 characters of the body's text. |
| `preview_html` | string | 否 | First 3000 characters of content_html (thumbnails). |
| `children_count` | integer | 否 | Active items directly inside the folder. |
| `filename` | string | 否 | Original filename at upload time. |
| `content_type` | string \| null | 否 |  |
| `byte_size` | integer | 否 |  |
| `kind` | `image` \| `video` \| `audio` \| `pdf` \| `text` \| `spreadsheet` \| `word` \| `presentation` \| `archive` \| `other` | 否 |  |
| `extension` | string \| null | 否 |  |
| `previewable` | boolean | 否 |  |
| `width` | integer \| null | 否 |  |
| `height` | integer \| null | 否 |  |
| `download_url` | string | 否 |  |
| `raw_url` | string | 否 |  |
| `content_url` | string | 否 |  |
| `thumbnail_url` | string \| null | 否 |  |
| `preview_url` | string \| null | 否 |  |

### Attachment

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | string | 是 | Attachment token; also the :id of the GET endpoints |
| `url` | string | 是 |  |
| `preview_url` | string | 是 |  |
| `raw_url` | string | 是 |  |
| `embed_html` | string | 是 | Paste verbatim into content_markdown (as its own paragraph) to render the app's attachment card in a document. Writing plain links to the attachment URLs renders as links, not the card. |

### FolderCrumb

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | string (uuid) | 是 |  |
| `public_id` | string \| null | 是 |  |
| `title` | string | 是 |  |

### Reaction

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `id` | string (uuid) | 是 |  |
| `actor_id` | string (uuid) | 是 |  |
| `reactable_id` | string (uuid) | 是 |  |
| `reactable_type` | string | 是 |  |
| `reaction_type_id` | string (uuid) | 是 |  |
| `reaction_emoji` | string | 是 |  |
| `reaction_name` | string | 是 |  |
| `created_at` | string (date-time) | 是 |  |
| `updated_at` | string (date-time) | 是 |  |
