← 文档

Writing API · v1

AI 写作 API

生成 Markdown,提交草稿,在本站预览与发布。

用途

让外部 AI、脚本或工作流把生成的 Markdown 写入本站草稿。站点负责校验、保存、预览和发布;模型调用由外部程序完成,不绑定某一家 AI,也不会替你调用付费模型。

API 版本为 v1。现阶段支持创建草稿、按 slug 查询文章、局部修改草稿;已发布文章的修改和发布操作在站长后台完成。普通文章、项目、解题复盘、生活均可提交。

启用

推荐在站长后台「写作密钥」页面创建密钥:填一个名称(如「笔记本脚本」),创建后密钥只显示一次,复制保存即可使用。密钥以 0w0_wk_ 开头,服务端只保存哈希;以创建它的站长身份提交,最多保留 10 个。删除密钥会立即生效,使用它的程序随即返回 401。页面会显示每个密钥最近一次使用的时间(精确到分钟)。

也可以继续使用环境变量配置一个固定密钥(旧方式,与后台密钥同时有效):

dotenv
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。

json
{
  "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 后,在配置好环境变量的终端执行:

bash
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:

powershell
$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,例如:

json
{
  "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 时,已保存的项目元数据会被清除。
bash
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。查询只覆盖密钥所属站长的内容。

状态码含义
400JSON、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 和后台编辑路径,供手工检查或在隔离数据库整体清理时核对。不要对包含正式内容的数据库运行。

本机回环地址可以直接运行:

powershell
$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

非回环地址默认拒绝执行。只有确认目标是隔离测试环境时,才显式设置测试标记:

bash
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)。正文使用 ![说明](/media/图片UUID)。外链图片会被当前内容安全策略拦截。
  • 视频:后台上传 MP4、MOV、WebM,原片最多 1 GiB、2 分钟,浏览器会先在本机压缩为 720p MP4 再上传(压缩期间不要关闭页面)。正文使用 ![视频](/media/视频UUID#video),渲染为播放器;视频不能作为封面。原始 <video>、iframe 仍会被清洗。
  • 代码:可以显示代码块,目前未启用语法高亮。
  • 公式、Mermaid:尚未接入公式或图表渲染插件,会作为普通文本或代码显示。
  • HTML 与 MDC 自定义组件:不作为写作 API 的支持契约,危险内容会被清洗。请让 AI 输出 Markdown 正文,不要依赖 HTML 嵌入或页面组件。

“完整 Markdown”没有覆盖所有扩展的统一含义;调用方应按以上范围生成内容,并在后台使用实际预览检查排版。