API 文档
密钥管理概述
外部系统通过 /openapi/v1 维护分类、项目与文档。所有请求需携带以下签名头:
Base URL:所有接口都基于
https://22qalpohd8v7x.app.jcloudcs.com/openapi/v1,请求需携带签名请求头快速开始
使用开放 API 只需 3 步:
- 在 密钥管理 新建密钥,获得 AppKey 与 Secret
- 按下方签名算法构造
X-Signature等 4 个请求头 - 调用
/openapi/v1接口,以密钥属主身份读写文档
三种语言的完整签名请求示例(以文档列表接口为例):
# 先计算签名(详见下方「签名算法」)
TS=$(date +%s) NONCE=$(openssl rand -hex 16)
SIGN=$(printf "%s\n%s\n%s\n%s\n%s\n%s" "$APP_KEY" "GET" "/openapi/v1/docs" "$TS" "$NONCE" \
"$(printf '' | sha256sum | cut -d' ' -f1)" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')
curl "$BASE_URL/openapi/v1/docs?page=1&size=15" \
-H "X-App-Key: $APP_KEY" \
-H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" \
-H "X-Signature: $SIGN"
import hashlib, hmac, time, uuid, requests
BASE, APP_KEY, SECRET = "https://your-server", "ak_xxx", "sk_xxx"
method, path, body = "GET", "/openapi/v1/docs", ""
ts, nonce = str(int(time.time())), uuid.uuid4().hex
sts = "\n".join([APP_KEY, method, path, ts, nonce,
hashlib.sha256(body.encode()).hexdigest()])
sign = hmac.new(SECRET.encode(), sts.encode(), hashlib.sha256).hexdigest()
print(requests.get(BASE + path, params={"page": 1, "size": 15}, headers={
"X-App-Key": APP_KEY, "X-Timestamp": ts, "X-Nonce": nonce, "X-Signature": sign,
}).json())
const crypto = require("crypto");
const BASE = "https://your-server", APP_KEY = "ak_xxx", SECRET = "sk_xxx";
const method = "GET", path = "/openapi/v1/docs", body = "";
const ts = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomBytes(16).toString("hex");
const sts = [APP_KEY, method, path, ts, nonce,
crypto.createHash("sha256").update(body).digest("hex")].join("\n");
const sign = crypto.createHmac("sha256", SECRET).update(sts).digest("hex");
fetch(`${BASE}${path}?page=1&size=15`, { headers: {
"X-App-Key": APP_KEY, "X-Timestamp": ts, "X-Nonce": nonce, "X-Signature": sign,
}}).then((r) => r.json()).then(console.log);
签名请求头
| 请求头 | 说明 |
|---|---|
| X-App-Key | 身份标识(创建密钥时生成) |
| X-Timestamp | 当前 Unix 秒,与服务器偏差须在 5 分钟内 |
| X-Nonce | 8~64 位随机串,5 分钟内不可重复(防重放) |
| X-Signature | 按下述算法计算的 hex 签名 |
签名算法
签名算法:sign = HMAC-SHA256(Secret, AppKey + LF + METHOD + LF + path + LF + timestamp + LF + nonce + LF + sha256hex(body)),LF 为换行符;无请求体时 body 为空串。
# 先计算签名(待签串见上方说明)
TS=$(date +%s) NONCE=$(openssl rand -hex 16)
SIGN=$(printf "%s\n%s\n%s\n%s\n%s\n%s" "$APP_KEY" "GET" "/openapi/v1/docs" "$TS" "$NONCE" \
"$(printf '' | sha256sum | cut -d' ' -f1)" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')
curl "https://22qalpohd8v7x.app.jcloudcs.com/openapi/v1/docs?page=1&size=15" \
-H "X-App-Key: $APP_KEY" \
-H "X-Timestamp: $TS" \
-H "X-Nonce: $NONCE" \
-H "X-Signature: $SIGN"
可用接口
所有操作均以密钥属主身份执行,只能访问本人的资源。成功返回 {"data": …},失败返回 {"error": "错误信息"}。
分类
GET
/openapi/v1/categories
获取我的分类列表(按 sort、id 升序)
响应示例
{
"data": [
{
"id": 1,
"name": "产品文档",
"sort": 0,
"owner_id": 2,
"doc_count": 5
}
]
}
POST
/openapi/v1/categories
新建分类;同级分类名不可重复
请求示例
{
"name": "产品文档",
"sort": 0
}
响应示例
{
"data": {
"id": 1,
"name": "产品文档",
"sort": 0,
"owner_id": 2,
"doc_count": 0
}
}
PUT
/openapi/v1/categories/:id
修改分类
请求示例
{
"name": "产品文档(改名)",
"sort": 10
}
响应示例
{
"data": {
"id": 1,
"name": "产品文档(改名)",
"sort": 10,
"owner_id": 2,
"doc_count": 5
}
}
DELETE
/openapi/v1/categories/:id
删除分类,其下文档回到未分类
响应示例
{
"ok": true
}
项目
GET
/openapi/v1/projects
获取我的项目列表(按更新时间倒序)
响应示例
{
"data": [
{
"id": 3,
"name": "2026 规划",
"description": "年度规划文档",
"owner_id": 2,
"doc_count": 8,
"created_at": "2026-01-05T10:00:00+08:00",
"updated_at": "2026-09-01T14:30:00+08:00"
}
]
}
POST
/openapi/v1/projects
新建项目
请求示例
{
"name": "2026 规划",
"description": "年度规划文档"
}
响应示例
{
"data": {
"id": 3,
"name": "2026 规划",
"description": "年度规划文档",
"owner_id": 2,
"doc_count": 0,
"created_at": "2026-09-24T09:00:00+08:00",
"updated_at": "2026-09-24T09:00:00+08:00"
}
}
PUT
/openapi/v1/projects/:id
修改项目
请求示例
{
"name": "2026 规划(修订)",
"description": "年度规划与里程碑"
}
响应示例
{
"data": {
"id": 3,
"name": "2026 规划(修订)",
"description": "年度规划与里程碑",
"owner_id": 2,
"doc_count": 8,
"updated_at": "2026-09-24T10:00:00+08:00"
}
}
DELETE
/openapi/v1/projects/:id
删除项目,其下文档回到未分组(不级联删除)
响应示例
{
"ok": true
}
文档
GET
/openapi/v1/docs
分页获取文档列表,不含正文
响应示例
{
"data": [
{
"id": 12,
"title": "接入指南",
"version": "v1.0.0",
"slug": "aB3xY9kQ",
"owner_id": 2,
"project_id": 3,
"category_id": 1,
"is_shared": false,
"view_count": 16,
"created_at": "2026-08-12T09:20:00+08:00",
"updated_at": "2026-09-20T18:02:00+08:00"
}
],
"total": 42,
"page": 1,
"size": 20
}
GET
/openapi/v1/docs/:id
获取文档详情,含 Markdown 正文
响应示例
{
"data": {
"id": 12,
"title": "接入指南",
"version": "v1.0.0",
"slug": "aB3xY9kQ",
"type": "markdown",
"content": "# 接入指南\n\n正文 Markdown…",
"owner_id": 2,
"project_id": 3,
"category_id": 1,
"is_shared": false,
"view_count": 16,
"created_at": "2026-08-12T09:20:00+08:00",
"updated_at": "2026-09-20T18:02:00+08:00"
}
}
POST
/openapi/v1/docs
新建文档;type 可选 markdown(默认)/ mindmap / board,content 为对应类型内容;version 由系统自动生成(v1.0.0 起,每次内容保存 +1)
请求示例
{
"title": "接入指南",
"type": "markdown",
"content": "# 接入指南\n\n正文 Markdown…",
"project_id": 3,
"category_id": 1
}
响应示例
{
"data": {
"id": 12,
"title": "接入指南",
"version": "v1.0.0",
"slug": "aB3xY9kQ",
"content": "# 接入指南\n\n正文 Markdown…",
"owner_id": 2,
"project_id": 3,
"category_id": 1,
"is_shared": false,
"view_count": 0
}
}
POST
/openapi/v1/docs/from-url
从公网链接抓取正文并创建可编辑的 Markdown 文档(JSON:url 必填,title / project_id / category_id 可选;拒绝内网与保留地址)
请求示例
{
"url": "https://example.com/guide",
"title": "接入指南",
"project_id": 3,
"category_id": 1
}
响应示例
{
"data": {
"id": 14,
"title": "接入指南",
"version": "v1.0.0",
"slug": "mN4pQ8sT",
"type": "markdown",
"content": "# 接入指南\n\n> 来源:https://example.com/guide\n\n正文…",
"owner_id": 2,
"project_id": 3,
"category_id": 1,
"is_shared": false,
"view_count": 0
}
}
POST
/openapi/v1/docs/html
上传 zip 创建 HTML 整站文档(multipart 表单:title、file、project_id?、category_id?;站点根须含 index.html)。站点文件不支持在线编辑,可经更新接口改标题/归属
请求示例
Content-Type: multipart/form-data; boundary=----docshare ------docshare Content-Disposition: form-data; name="title" 产品官网 ------docshare Content-Disposition: form-data; name="file"; filename="dist.zip" Content-Type: application/zip <zip 二进制内容> ------docshare--
响应示例
{
"data": {
"id": 13,
"title": "产品官网",
"slug": "kQ3xZ9mP",
"type": "html",
"content": "{\"prefix\":\"html/13/ab3f/\",\"entry\":\"index.html\",\"files\":[\"index.html\"]}",
"owner_id": 2,
"project_id": 3,
"category_id": 1,
"is_shared": false,
"view_count": 0
}
}
POST
/openapi/v1/docs/html-page
创建单页 HTML(试卷/简历等):JSON 传 title + 完整自包含 html(CSS/JS 内联);页面写入对象存储,content 为 manifest。与站内智能体 create_html_page 对齐
请求示例
{
"title": "一年级数学卷",
"html": "<!DOCTYPE html><html><head><meta charset=\"utf-8\"><style>body{font-family:sans-serif}</style></head><body><h1>试卷</h1></body></html>",
"project_id": 3,
"category_id": 1
}
响应示例
{
"data": {
"id": 15,
"title": "一年级数学卷",
"slug": "pL7nR2wX",
"type": "html",
"content": "{\"prefix\":\"html/15/cd9e/\",\"entry\":\"index.html\",\"files\":[\"index.html\"],\"size\":120,\"count\":1}",
"owner_id": 2,
"project_id": 3,
"category_id": 1,
"is_shared": false,
"view_count": 0
}
}
GET
/openapi/v1/docs/:id/html-page
读取单文件 HTML 文档的入口页原文(仅单页站点;多文件整站请用 GET /docs/:id 看 manifest)
响应示例
{
"data": {
"id": 15,
"title": "一年级数学卷",
"type": "html",
"html": "<!DOCTYPE html><html>…</html>",
"content_version": 0,
"version": "v1.0.0",
"project_id": 3,
"category_id": 1
}
}
PUT
/openapi/v1/docs/:id/html-page
整页替换单文件 HTML 文档入口页(JSON:html + base_version,取自 GET 的 content_version;冲突返回 409;仅属主/管理员)。与站内智能体 replace_html_page 对齐
请求示例
{
"html": "<!DOCTYPE html><html><head><meta charset=\"utf-8\"></head><body><h1>修订版</h1></body></html>",
"base_version": 0
}
响应示例
{
"data": {
"id": 15,
"content_version": 1,
"version": "v1.0.1",
"manifest": {
"prefix": "html/15/ef12/",
"entry": "index.html",
"files": ["index.html"],
"size": 98,
"count": 1
}
}
}
PUT
/openapi/v1/docs/:id
修改文档;未传字段不修改,project_id / category_id 传 0 表示清空归属;version 由系统自动递增,传入值被忽略
请求示例
{
"title": "接入指南(修订)",
"content": "# 接入指南 v2\n\n更新后的正文…",
"project_id": 0,
"category_id": 1
}
响应示例
{
"data": {
"id": 12,
"title": "接入指南(修订)",
"version": "v1.0.1",
"slug": "aB3xY9kQ",
"type": "markdown",
"content": "# 接入指南 v2\n\n更新后的正文…",
"owner_id": 2,
"project_id": 0,
"category_id": 1,
"is_shared": false,
"view_count": 16
}
}
DELETE
/openapi/v1/docs/:id
删除文档
响应示例
{
"ok": true
}
模板
GET
/openapi/v1/templates
获取模板列表(自己创建 + 绑定到参与项目的,按更新时间倒序,不含内容)
响应示例
{
"data": [
{
"id": 5,
"name": "周报模板",
"type": "markdown",
"owner_id": 2,
"project_id": 3,
"category_id": 1,
"created_at": "2026-09-28T10:00:00+08:00",
"updated_at": "2026-09-28T10:00:00+08:00"
}
],
"total": 1,
"page": 1,
"size": 20
}
GET
/openapi/v1/templates/:id
获取模板详情,含内容;可见即可读(属主 / 绑定项目的成员 / 管理员)
响应示例
{
"data": {
"id": 5,
"name": "周报模板",
"type": "markdown",
"content": "# 周报\n\n## 本周进展\n…",
"owner_id": 2,
"project_id": 3,
"category_id": 1
}
}
POST
/openapi/v1/templates
新建模板;type 支持 markdown(默认)/ mindmap / board / drawio(html 整站不支持);project_id 绑定项目后项目成员可共用
请求示例
{
"name": "周报模板",
"type": "markdown",
"content": "# 周报\n\n## 本周进展\n…",
"project_id": 3,
"category_id": 1
}
响应示例
{
"data": {
"id": 5,
"name": "周报模板",
"type": "markdown",
"owner_id": 2,
"project_id": 3,
"category_id": 1
}
}
PUT
/openapi/v1/templates/:id
修改模板(仅属主 / 管理员);未传字段不修改,project_id / category_id 传 0 表示解除绑定
请求示例
{
"name": "周报模板(修订)",
"content": "# 周报 v2\n…"
}
响应示例
{
"data": {
"id": 5,
"name": "周报模板(修订)",
"type": "markdown",
"owner_id": 2,
"project_id": 3,
"category_id": 1
}
}
DELETE
/openapi/v1/templates/:id
删除模板,不影响已创建的文档
响应示例
{
"ok": true
}
错误码
下表 error 信息为服务器原文返回,HTTP 状态码同时标注。
通用错误码(鉴权与协议层)
| 状态码 | error 信息 |
|---|---|
| 401 | 缺少签名头:X-App-Key / X-Timestamp / X-Nonce / X-Signature |
| 401 | 时间戳无效或与服务器偏差超过 5 分钟 |
| 401 | nonce 长度需在 8~64 之间 |
| 401 | AppKey 不存在或已禁用 |
| 400 | 读取请求体失败或超过 2MB |
| 401 | 签名校验失败 |
| 401 | nonce 已使用,疑似重放请求 |
| 401 | 密钥属主不存在或已禁用 |
业务错误码
| 状态码 | error 信息 |
|---|---|
| 400 | 参数错误 |
| 400 | 标题不能为空 |
| 400 | 项目或分类非法 |
| 400 | 项目非法或无权归属到该项目 |
| 400 | 分类非法或无权归属到该分类 |
| 400 | 创建失败,你的分类名已存在 |
| 400 | 更新失败,你的分类名已存在 |
| 400 | 分类 id 非法 |
| 400 | 项目 id 非法 |
| 403 | 无权操作他人的分类 |
| 403 | 无权操作他人的项目 |
| 403 | 无权操作他人的文档 |
| 404 | 分类不存在 |
| 404 | 项目不存在 |
| 404 | 文档不存在 |
| 400 | 模板名称不能为空 |
| 400 | 模板名称不能超过 100 字 |
| 400 | 不支持的模板类型(HTML 整站不支持模板) |
| 400 | 模板内容过大(超过 2MB) |
| 400 | 模板 id 非法 |
| 403 | 无权操作他人的模板 |
| 404 | 模板不存在 |
| 500 | 创建失败 |
| 500 | 更新失败 |
| 500 | 删除失败 |