Writing API · v1
AI 写作 API
生成 Markdown,提交草稿,在本站预览与发布。
用途
让外部 AI、脚本或工作流把生成的 Markdown 写入本站草稿。站点负责校验、保存、预览和发布;模型调用由外部程序完成,不绑定某一家 AI,也不会替你调用付费模型。
API 版本为 v1。现阶段支持创建草稿、按 slug 查询文章、局部修改草稿;已发布文章的修改和发布操作在站长后台完成。普通文章、项目、解题复盘、生活均可提交。
启用
推荐在站长后台「写作密钥」页面创建密钥:填一个名称(如「笔记本脚本」),创建后密钥只显示一次,复制保存即可使用。密钥以 0w0_wk_ 开头,服务端只保存哈希;以创建它的站长身份提交,最多保留 10 个。删除密钥会立即生效,使用它的程序随即返回 401。页面会显示每个密钥最近一次使用的时间(精确到分钟)。
也可以继续使用环境变量配置一个固定密钥(旧方式,与后台密钥同时有效):
NUXT_WRITING_API_KEY=
NUXT_WRITING_API_OWNER_ID=
NUXT_WRITING_API_KEY:独立随机密钥,使用 32–512 个可见 ASCII 字符;不要包含空格,不要使用登录密码或模型服务商的 API Key,也不要以0w0_wk_开头。可以用node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"在自己的终端生成。NUXT_WRITING_API_OWNER_ID:已有、未停用的站长账号 UUID。可在数据库执行SELECT id, email FROM users WHERE role = 'owner' AND status = 'active';查询;接口不能指定作者。
修改环境变量后重启应用。Docker Compose 已透传这两个变量。既没有后台密钥也没有环境变量时接口关闭,返回 404。设计预览模式不提供写作 API,也不会写入假数据。
调用管理域名,例如 https://admin.example.com。生产公共网关屏蔽 /api/admin,在公开域名调用会返回 404。本地开发可使用 http://127.0.0.1:3000。
每次请求附带 Authorization: Bearer <密钥>。只使用服务端程序调用,不把密钥放进网页。接口不依赖登录 Cookie,也不开放跨域浏览器调用;机器请求无需伪造 Origin。其他后台接口仍使用原有登录与来源校验。
接口
| 方法 | 路径 | 用途 |
|---|---|---|
| GET | /api/admin/writing/v1 | 检查鉴权、版本、限制和请求 schema |
| POST | /api/admin/writing/v1/articles | 创建 Markdown 草稿 |
| GET | /api/admin/writing/v1/articles/{slug} | 查询绑定作者的文章原文和状态 |
| PATCH | /api/admin/writing/v1/articles/{slug} | 局部修改绑定作者的草稿 |
所有接口均需要密钥。每个绑定作者每分钟最多 30 次请求,另有 IP 限制;多实例部署时应用内计数分别计算,网关还会执行自己的限制。
创建草稿
请求体为 application/json,整个请求体最多 2 MiB。
{
"slug": "ai-docker-notes-20260912",
"title": "Docker 排查实践笔记",
"summary": "记录一次容器启动失败的定位过程、关键证据和最终解决方式。",
"markdown": "## 背景\n\n这里是 AI 生成后整理的正文。\n\n- 保留排查证据\n- 记录验证结果\n",
"kind": "general",
"category": "工程实践",
"categories": ["学习笔记"],
"tags": ["Docker", "排查"]
}
| 字段 | 必填 | 约束 |
|---|---|---|
slug | 是 | 1–120 字符,仅小写英文、数字、中文和分隔用短横线;同一次生成与重试始终使用同一个值 |
title | 是 | 2–160 字符 |
summary | 是 | 10–360 字符 |
markdown | 是 | 非空,最多 500,000 字符 |
kind | 否 | general、project、solution、life;默认 general |
category | 否 | 主分类名称,最多 64 字符 |
categories | 否 | 全部分类名称数组,每个最多 64 字符;与 category 合并去重后最多 5 个,category 排在第一位作为主分类 |
tags | 否 | 最多 10 个,每个 1–48 字符 |
coverId | 否 | 已在本站上传的文章图片 UUID,或 null |
status | 否 | 仅接受 draft,省略也是草稿 |
project | 否 | 仅项目类型使用的元数据:repoUrl、demoUrl、stack、status;省略时不显示未填写的信息 |
项目元数据中的 status 表示项目进展,与文章是否发布不同:planned(计划中)、active(开发中)、maintenance(维护中)、archived(已归档)。repoUrl 和 demoUrl 为项目的 HTTP/HTTPS 地址,stack 为技术名称数组。具体限制可读取版本接口返回的 JSON Schema。
版本接口返回的是请求输入 schema,带默认值的 status、kind、category、categories 和 tags 不会被误标成必填。标签会按 Unicode 兼容形式与大小写去重并保留第一次写法,项目技术栈只移除完全相同的重复值;非 project 类型提交 project 字段会返回 400,避免元数据被静默丢弃。
未知字段会被拒绝。没有图片时不需要提交 coverId。上传图片仍通过站长后台完成,本模块暂不提供机器上传接口。
将 JSON 保存为 UTF-8 的 article.json 后,在配置好环境变量的终端执行:
curl --fail-with-body "$WRITING_BASE_URL/api/admin/writing/v1/articles" \
-H "Authorization: Bearer $WRITING_API_KEY" \
-H 'Content-Type: application/json' \
--data-binary @article.json
其中 WRITING_BASE_URL 为管理域名,WRITING_API_KEY 为调用方保存的写作密钥。Windows PowerShell 可以使用 Invoke-RestMethod:
$headers = @{ Authorization = "Bearer $env:WRITING_API_KEY" }
Invoke-RestMethod -Method Post `
-Uri "$env:WRITING_BASE_URL/api/admin/writing/v1/articles" `
-Headers $headers -ContentType 'application/json; charset=utf-8' `
-InFile ./article.json
创建成功返回 201,例如:
{
"id": "cfe0eac0-df88-4be8-93f9-36e2216fc28b",
"slug": "ai-docker-notes-20260912",
"status": "draft",
"editPath": "/admin/articles/cfe0eac0-df88-4be8-93f9-36e2216fc28b"
}
用管理域名拼接 editPath,登录后预览、修改和发布。草稿不会出现在公开文章列表里。
修改草稿
PATCH /api/admin/writing/v1/articles/{slug} 只提交要改的字段,未提交的字段保持原值,不需要重新上传整篇文章。请求体格式、大小限制与创建相同,可修改 title、summary、markdown、kind、project、category、categories、tags、coverId,完整 schema 见版本接口返回的 updateSchema。
- slug 位于路径中,不能修改;请求体带
slug或status会返回400,空对象也返回400。 - 只能修改草稿。文章已发布时返回
409,请到后台修改,泄露的密钥因此无法改动公开内容。 category与categories是同一份分类列表:提交其中任一个,会用本次提交的值整体替换原有分类。tags同样整体替换。coverId传null移除封面;把kind改为非project时,已保存的项目元数据会被清除。
curl --fail-with-body -X PATCH "$WRITING_BASE_URL/api/admin/writing/v1/articles/ai-docker-notes-20260912" -H "Authorization: Bearer $WRITING_API_KEY" -H 'Content-Type: application/json' --data-binary '{"title":"Docker 排查实践笔记(修订)","tags":["Docker","排查","容器"]}'
成功返回 200,响应内容与查询接口相同(含 editPath)。
重试与查询
每篇内容使用固定 slug。同一个 slug 再次创建返回 409,不会覆盖旧内容或自动生成另一篇。
如果请求超时,先调用 GET /api/admin/writing/v1/articles/{slug}:返回 200 时核对原文;返回 404 时再重试创建。若并发重试得到 409,再次查询即可。不要因为一次超时就更换 slug。
查询返回 id、slug、title、summary、markdown、kind、category(主分类)、categories、tags、coverId、project、status、updatedAt 和 editPath。站长在后台发布后,查询中的状态会变为 published。查询只覆盖密钥所属站长的内容。
| 状态码 | 含义 |
|---|---|
| 400 | JSON、slug 或字段校验失败;字段错误可查看 data.fields |
| 401 | 未提供或提供了错误的密钥 |
| 403 | 密钥对应的站长账号已停用或不再是站长,或请求来源不受信任 |
| 404 | 接口未启用、从公共网关访问,或文章不存在 |
| 409 | 创建时 slug 已存在,先查询,不要盲目重复创建;修改时文章已发布 |
| 413 | 请求体超过 2 MiB |
| 415 | 不是 application/json |
| 429 | 触发频率限制,请稍后重试 |
| 503 | 当前处于只读设计预览模式 |
真实接口冒烟验证
仓库提供 scripts/verify-writing-api.mjs,用于已配置真实 PostgreSQL 的隔离环境。脚本会检查输入 schema、鉴权、草稿创建与读取、标签和技术栈去重、固定 slug 冲突、非法发布、项目字段边界,以及草稿不会出现在公开详情和列表中。
脚本会创建并保留两个带随机 slug 的草稿,不会自动删除任何数据。结束时会输出草稿 ID、slug 和后台编辑路径,供手工检查或在隔离数据库整体清理时核对。不要对包含正式内容的数据库运行。
本机回环地址可以直接运行:
$env:WRITING_BASE_URL = "http://127.0.0.1:3000"
$env:WRITING_API_KEY = "replace-with-isolated-test-key-32-chars-minimum"
node scripts/verify-writing-api.mjs
非回环地址默认拒绝执行。只有确认目标是隔离测试环境时,才显式设置测试标记:
WRITING_BASE_URL=https://admin-test.example.com \
WRITING_API_KEY="$TEST_WRITING_API_KEY" \
WRITING_API_TEST_ENV=1 \
node scripts/verify-writing-api.mjs
脚本不会输出密钥,也不接受 URL 中携带账号密码、路径、查询参数或片段。
Markdown 与媒体边界
当前渲染支持常用 Markdown:标题、段落、粗体、斜体、引用、列表、分割线、链接、图片、行内代码和代码块,以及表格、任务列表、删除线等扩展。
- 图片:后台上传 JPEG、PNG、WebP、GIF,单张文章图片最多 10 MiB;会转换为 WebP,GIF 保留动画(最长边 960)。正文使用
。外链图片会被当前内容安全策略拦截。 - 视频:后台上传 MP4、MOV、WebM,原片最多 1 GiB、2 分钟,浏览器会先在本机压缩为 720p MP4 再上传(压缩期间不要关闭页面)。正文使用
,渲染为播放器;视频不能作为封面。原始<video>、iframe仍会被清洗。 - 代码:可以显示代码块,目前未启用语法高亮。
- 公式、Mermaid:尚未接入公式或图表渲染插件,会作为普通文本或代码显示。
- HTML 与 MDC 自定义组件:不作为写作 API 的支持契约,危险内容会被清洗。请让 AI 输出 Markdown 正文,不要依赖 HTML 嵌入或页面组件。
“完整 Markdown”没有覆盖所有扩展的统一含义;调用方应按以上范围生成内容,并在后台使用实际预览检查排版。