公共接口与 UDOC 生成指南
从理解,到动手实现。步骤、协议与可复制的示例,都在这份文档中。
版本:2026-09-12
规范入口:https://app.unidoc.top/
本文只描述当前实现中已经存在并可验证的接口。内部 AI 编排协议、编辑器 DOM、encodeUdocPackage() 等未导出的函数不属于公共兼容性承诺。
1. 能力总览
| 能力 | 当前状态 | 推荐用途 |
|---|---|---|
| 原始字节格式转换 HTTP API | 已提供 | Markdown、DOCX、UDOC 相互转换 |
iframe postMessage 桥 | 已提供 | 宿主页面整档装载、读取 HTML/JSON |
window.UniDocConverters | 已提供 | 在 UniDoc 页面内调用格式转换 |
| UDOC JSON Authoring v1 | 已提供 | AI/人工生成可审阅的非压缩交换稿 |
Python tools/udoc_json.py | 已提供 | 校验、打包和解包 .udoc.json |
公网 UDOC JSON <-> UDOC HTTP API | 已提供 | 受认证、配额和请求尺寸限制的转换 |
| 细粒度文档 AI MCP(OAuth + JSON-RPC) | 已提供 | 按文稿、段落和原生批注进行受保护的 AI 读写 |
这里的“公共”表示接口不依赖 UniDoc 内部模块变量,并不代表无限额、无认证或永久
兼容。生产部署可按账号、来源、配额和风控策略收紧访问。
2. 公共格式转换 HTTP API
所有接口都使用原始请求体,不使用 multipart/form-data。Markdown 必须是 UTF-8;
二进制接口直接传文件字节。单次转换的当前上限为 200 MiB。
| 方法与路径 | 请求体 | 成功响应 |
|---|---|---|
POST /convert/md2unidoc | UTF-8 Markdown | UDOC,application/vnd.unidoc |
POST /convert/md2unido | UTF-8 Markdown | 同上,兼容别名 |
POST /convert/md2docx | UTF-8 Markdown | DOCX |
POST /convert/md2html | UTF-8 Markdown | 独立 HTML |
POST /convert/unidoc2md | UDOC 字节(纯文字或已托管静态图) | UTF-8 Markdown;待截图或待托管媒体返回 422 |
POST /convert/unidoc2word | UDOC 字节 | DOCX |
POST /convert/udoc2docx | UDOC 字节 | DOCX,兼容路径 |
POST /convert/docx2udoc | DOCX 字节 | UDOC |
示例:
curl --fail-with-body \
-H "Content-Type: text/markdown; charset=utf-8" \
--data-binary @README.md \
https://app.unidoc.top/convert/md2unidoc \
--output README.udoc
curl --fail-with-body \
-H "Content-Type: application/octet-stream" \
--data-binary @README.udoc \
https://app.unidoc.top/convert/unidoc2word \
--output README.docx失败响应通常是 JSON:
{ "error": "错误说明" }内部 Brotli / ZIP 部件接口只负责单个部件的压缩或解压,不能把一份
解包 JSON 自动组装成完整 UDOC,因此不应当被当作文档生成 API。
3. 页面内转换 API
在 UniDoc 页面同一个 JavaScript 上下文中,可以使用:
const html = await window.UniDocConverters.md2html('# 标题');
const udocBlob = await window.UniDocConverters.md2unidoc('# 标题');
const markdown = await window.UniDocConverters.unidoc2md(await file.arrayBuffer());
const wordBlob = await window.UniDocConverters.unidoc2word(await file.arrayBuffer());同时存在等名快捷函数:md2docx、md2html、md2unidoc、md2unido、unidoc2md、unidoc2word。文本结果是 Promise<string>,文档结果是Promise<Blob>。
浏览器 unidoc2md 统一走静态文章流程:动态 HTML、Canvas 和公式转成 PNG,图片发布到当前用户的 OmniDoc/R2,再写入可撤销的公开地址。图片发布需要登录及云文件写入、读取、分享权限。纯 HTTP POST /convert/unidoc2md 没有浏览器画面,待截图或待托管的输入返回 422 portable_media_requires_browser。操作和限制见 公众号排版与通用 Markdown。
这些函数属于 UniDoc 页面,不会自动注入任意第三方网页。跨站宿主应使用下一节的
iframe 协议或直接调用 HTTP API。
4. iframe 文档桥
完整的 URL 参数、实时缩放、全屏回退和可运行示例见 内嵌接入文档。
嵌入入口:
<iframe
id="unidoc"
src="https://app.unidoc.top/?embed=1&embedView=flow"
title="UniDoc 编辑器">
</iframe>宿主向 iframe 发送消息:
const frame = document.getElementById('unidoc');
const origin = 'https://app.unidoc.top';
frame.contentWindow.postMessage({
type: 'unidoc:set',
id: 'load-1',
doc: {
format: 'udoc',
version: 3,
unidoc_type: 'docs',
title: 'AI 生成文档',
blocks: [
{ para_id: 1, html: '<h1>标题</h1>' },
{ para_id: 2, html: '<p>正文</p>' }
]
}
}, origin);支持的消息:
| 方向 | type | 字段与语义 |
|---|---|---|
| 宿主 → iframe | unidoc:set | doc 为解包 JSON、html 为待导入 HTML,或 udoc 为原生 udoc3:BASE64 / 交换 JSON 字符串 |
| 宿主 → iframe | unidoc:get | 请求流式、不可编辑 HTML;可带 id |
| 宿主 → iframe | unidoc:get-udoc | 推荐传 format:'udoc3' 取原生包;不传则取交换 JSON 字符串;可带 id |
| iframe → 宿主 | unidoc:ready | 正文桥可用,URL 模板处理已结束;不代表最终视觉排版完成 |
| iframe → 宿主 | unidoc:content-set | unidoc:set 已应用 |
| iframe → 宿主 | unidoc:content | html 返回值 |
| iframe → 宿主 | unidoc:udoc | 原生路径返回 format:'udoc3' 和 udoc:'udoc3:BASE64';否则 udoc 为交换 JSON 字符串 |
| iframe → 宿主 | unidoc:change | 文档发生变化,约 400 ms 防抖 |
| iframe → 宿主 | unidoc:error | error 为错误说明 |
宿主必须校验消息来源:
window.addEventListener('message', event => {
if (event.origin !== 'https://app.unidoc.top') return;
if (event.source !== document.getElementById('unidoc').contentWindow) return;
console.log(event.data);
});匿名协作分享链接是服务端强制只读的。此时键盘、粘贴、拖放、编辑工具和unidoc:set 都不能修改文档;WebSocket 票据同样拒绝任何更新。读取、复制、缩放、
放映和导出仍可使用。
5. UDOC JSON Authoring v1:AI 可写交换格式
.udoc.json 是面向 AI、脚本和代码审阅的非压缩交换格式,MIME 为application/vnd.unidoc.udoc+json。它不是 UniDoc 的本地保存格式:用户执行保存、
另存为、Vault 保存或 OmniDoc 云保存时,系统必须重新验证并编码为带有正式容器签名的
UDOC 混合压缩包。把普通 JSON 改名为 .udoc 必须拒绝,不能降级读取。
正式 JSON Schema:schemas/udoc-json/v1.json,公网 $id 为https://app.unidoc.top/schemas/udoc-json/v1.json。最小可用文档:
{
"$schema": "https://app.unidoc.top/schemas/udoc-json/v1.json",
"format": "udoc",
"version": 3,
"unidoc_type": "docs",
"basename": "AI生成报告",
"page": {
"size": "A4",
"orientation": "portrait",
"marginPreset": "normal",
"margins": { "top": 76, "right": 76, "bottom": 76, "left": 76 }
},
"blocks": [
{ "para_id": 1, "html": "<h1>标题</h1>" },
{ "para_id": 2, "html": "<p>正文</p>" }
]
}作者必须遵守这些可执行约束:
format、version、unidoc_type必须分别严格为udoc、整数3、docs;para_id是正整数,在整份文档中唯一且长期稳定;不要使用"b12"或数字字符串;- 每个
blocks[].html必须只有一个顶层元素,例如一个<p>、<h1>、<table>
或 <div>;完整网页不能伪装成普通段落块;
- 普通块禁止
<script>、iframe/插件标签、on*事件属性、javascript:URL 和 CSS
活动表达式;完整可执行网页应放在隔离沙箱的 textboxes[kind=htmledit].config.html;
- 内联图片、音视频和附件使用
data:URL。打包器把它们改写为
media/<sha256>.<ext> 或 attachments/<sha256>.<ext>,去重并生成关系;
- 解包时也可使用顶层
resources对象:键是内容寻址包内路径,值是自包含 data URL。
路径哈希必须与解码后的字节完全一致;
wordCache、wordSourceParts、wordSettings是 Word 导入产生的派生缓存,AI 稿不得
写入。转换器会从 UDOC 导出交换稿时剔除它们;
- 当前实现限制为 100,000 块、单块 2 MiB、HTML 合计 64 MiB、单资源 32 MiB、
资源合计 64 MiB、JSON 嵌套 48 层。服务端还可按账号设置更低配额。
自由 HTML 应作为独立网页源码,而不是拆成伪段落:
{
"id": "site-home",
"kind": "htmledit",
"config": {
"webPage": true,
"freeHtmlSource": true,
"html": "<!doctype html><html><head><title>My App</title></head><body><h1>Hello</h1></body></html>"
}
}打包后 config.html 会变为内容寻址的 embeds/<sha256>.html,原始网页字节进入
UDOC。编辑预览必须继续使用隔离源沙箱;不得把 allow-scripts 与allow-same-origin 同时授予同一个不可信 iframe。
5.1 本地 CLI 和 Python API
依赖:python -m pip install brotli。CLI:
python tools/udoc_json.py validate report.udoc.json
python tools/udoc_json.py pack report.udoc.json report.udoc
python tools/udoc_json.py unpack report.udoc report.udoc.json --pretty
python tools/udoc_json.py unpack report.udoc report-inline.udoc.json --inline-media --prettyunpack 默认把包内二进制写成顶层 resources 的 data URL,仍是一份自包含 JSON;--inline-media 会进一步把图片、视频、音频和附件引用放回对应 HTML/对象字段。
HTML embeds 与其他源码资源仍保留在 resources,避免把整页源码塞进 HTML 属性。
模块 API:
from tools.udoc_json import (
load_authoring_json,
validate_authoring_document,
authoring_to_package,
package_to_authoring,
)
draft = load_authoring_json("report.udoc.json")
validated_copy = validate_authoring_document(draft)
authoring_to_package(validated_copy, "report.udoc")
roundtrip = package_to_authoring("report.udoc", inline_media=True)第三方生成文档应优先使用上述版本化 Authoring API;
它生成相同的严格 UDOC 容器。不要自行拼接 Brotli/ZIP 字节,也不要调用页面内部encodeUdocPackage(),后者不是版本化公共 API。
5.2 受认证的公网转换 API
三个端点当前只接受 app.unidoc.top 的已登录浏览器会话,并强制执行精确同源/CSRF
校验;服务端尚未把 OAuth Bearer Token 暴露为这一组接口的公共认证方式,也不提供
匿名跨域转换代理。请求不得引用远程媒体 URL,调用方应先将资源转为 data URL,避免
服务器替调用方任意抓取公网或内网地址。
| 方法与路径 | 请求体 | 成功响应 |
|---|---|---|
POST /validate/udoc-json | UTF-8 .udoc.json | JSON 校验结果与块数 |
POST /convert/udoc-json2udoc | UTF-8 .udoc.json | 严格 UDOC 二进制容器 |
POST /convert/udoc2udoc-json | UDOC 字节 | .udoc.json,application/vnd.unidoc.udoc+json |
POST /convert/udoc2udoc-json?inline_media=1 请求内联图片、音视频和附件。转换失败返回
非 2xx JSON,例如 { "error": "$.blocks[2].para_id: 重复的 para_id:2" }。
// 在已登录的 https://app.unidoc.top 页面中调用;跨域页面会被拒绝。
const response = await fetch('/convert/udoc-json2udoc', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/vnd.unidoc.udoc+json' },
body: JSON.stringify(draft),
});
if (!response.ok) throw new Error((await response.json()).error);
const udoc = await response.blob();服务端收到 JSON 后必须复用同一套校验器,并验证生成文件的容器签名、版本和完整性;不能
信任文件扩展名,也不能把 JSON 原样写入 Vault/OmniDoc 存储。
5.3 保存原生 HTML 与发布静态网站
UniDoc 保存或导出的原生 HTML 中,“继续编辑”入口固定为https://app.unidoc.top/。当前页面域名、旧部署域名、文档内 <meta> 或 HTML 内容都
不能覆盖该地址,因而离线打开导出文件时也会回到规范生产入口。
自由 HTML 可以从编辑器发布为个人静态网站。生产发布按以下顺序执行:
- 将当前文档编码为严格 UDOC 容器,保存到当前用户的 OmniDoc 专属空间;
- 取得该对象的
sourceObjectId; - 以同源登录会话调用
POST /sites/publish,提交自包含 HTML、名称和
sourceObjectId;
- 服务端向 OmniDoc 精确验证当前用户对该对象的读取权限,验证通过后才生成
https://sites.unidoc.top/p/<id>/ 快照。
v1 只发布一份自包含 index.html;样式、脚本和媒体应内联,不支持上传可枚举目录或
任意服务端路径。sites.unidoc.top 是能力隔离的只读来源:它只响应已发布页面,不
暴露编辑器、登录、云文件或转换接口。
公开快照允许脚本、表单和下载,但以不透明来源沙箱运行,不能把sites.unidoc.top 的 Cookie 或 localStorage 当作页面自己的持久化存储。
“继续编辑”打开的 app.unidoc.top 新窗口不继承该沙箱;HTML 通过一次性的
窗口身份与随机令牌握手传入,不会把公开页面变成匿名服务端抓取代理。
源对象校验表示“当前用户可读取这个对象”,不等于服务端已经验证对象所有权、
UDOC 内容或它与 HTML 快照的语义对应关系;严格 UDOC 源保存由官方编辑器流程保证。
6. 对 AI/MCP 的适配评价
当前设计的优点是:文档模型是结构化块而不是画布像素,para_id 稳定,整档导入/导出
和二进制转换都有确定边界,因此浏览器 AI 比操作传统富文本 DOM 更容易获得可复现结果。
细粒度 MCP 已作为可选能力层提供。它不参与编辑器启动,也不替换 iframe 桥;客户端只有
在需要 AI 操作时才连接 /mcp。MCP 使用已验证的 UniDoc 会话或 OAuth Bearer grant,匿名
只读分享不能获得工具调用权限。写操作都携带 expectedRevision + requestId,因此并发修改
会返回可处理的冲突,重复请求会得到同一结果。
MCP 的工具、OAuth 发现地址、原生批注锚定和客户端示例见docs/MCP_PROTOCOL.md 与 docs/AI_MCP_INTEGRATION.md。
iframe 桥仍适合宿主页面整档装载;MCP 适合 AI 按 para_id、批注 ID 和版本进行局部操作。
7. 相关规范
- UDOC 容器规范:容器布局、分片、压缩和完整性规则。
- UniDoc MCP 协议:OAuth、JSON-RPC、细粒度工具和原生批注锚定。
- AI MCP 接入示例:客户端配置、调用顺序和 WASM/iframe 边界。
EMBED_MODE.md:嵌入模式、URL 模板和视图行为。COLLABORATION_PROTOCOL.md:协作房间、票据、只读分享和部署边界。