EmDash 在 /_emdash/api/ 暴露 REST API,用于内容管理、媒体上传和架构操作。
认证
API 请求需要通过 Authorization 头中的 Bearer 令牌进行认证:
Authorization: Bearer <token>
通过管理界面或编程方式生成令牌。
响应格式
所有响应遵循一致的格式。成功响应将结果包装在 data 中:
{
"success": true,
"data": { ... }
}
错误响应包含代码、消息和可选的详细信息:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message",
"details": { ... }
}
}
内容端点
列出内容
GET /_emdash/api/content/:collection
参数
| 参数 | 类型 | 描述 |
|---|---|---|
collection | string | 集合标识符(路径) |
cursor | string | 分页游标(查询) |
limit | number | 每页条目数(查询,默认:50) |
status | string | 按状态筛选(查询) |
orderBy | string | 排序字段(查询) |
order | string | 排序方向:asc 或 desc(查询) |
响应
{
"success": true,
"data": {
"items": [
{
"id": "01HXK5MZSN...",
"type": "posts",
"slug": "hello-world",
"data": { "title": "Hello World", ... },
"status": "published",
"createdAt": "2025-01-24T12:00:00Z",
"updatedAt": "2025-01-24T12:00:00Z"
}
],
"nextCursor": "eyJpZCI6..."
}
}
获取内容
GET /_emdash/api/content/:collection/:id
响应
{
"success": true,
"data": {
"item": {
"id": "01HXK5MZSN...",
"type": "posts",
"slug": "hello-world",
"data": { "title": "Hello World", ... },
"status": "published",
"createdAt": "2025-01-24T12:00:00Z",
"updatedAt": "2025-01-24T12:00:00Z"
}
}
}
创建内容
POST /_emdash/api/content/:collection
Content-Type: application/json
请求体
{
"data": {
"title": "New Post",
"content": [...]
},
"slug": "new-post",
"status": "draft"
}
响应
{
"success": true,
"data": {
"item": { ... }
}
}
更新内容
PUT /_emdash/api/content/:collection/:id
Content-Type: application/json
请求体
{
"data": {
"title": "Updated Title"
},
"status": "published"
}
删除内容
DELETE /_emdash/api/content/:collection/:id
响应
{
"success": true,
"data": {
"success": true
}
}
媒体端点
列出媒体
GET /_emdash/api/media?includeUsage=1
参数
| 参数 | 类型 | 描述 |
|---|---|---|
cursor | string | 不透明分页游标 |
limit | number | 每页条目数,1 到 100(默认:50) |
mimeType | string | 按一个或多个逗号分隔的 MIME 类型筛选 |
q | string | 不区分大小写的文件名搜索 |
includeUsage | 1 | 在每个返回项目上包含覆盖率感知的 usage 摘要 |
响应
{
"data": {
"items": [
{
"id": "01HXK5MZSN...",
"filename": "photo.jpg",
"mimeType": "image/jpeg",
"size": 102400,
"width": 1920,
"height": 1080,
"url": "/_emdash/api/media/file/uploads/photo.jpg",
"createdAt": "2025-01-24T12:00:00Z",
"usage": {
"count": 3,
"coverage": {
"scope": "all_content_collections",
"status": "complete"
}
}
}
],
"nextCursor": "eyJpZCI6..."
}
}
获取媒体
GET /_emdash/api/media/:id?includeUsage=1
includeUsage 在列表和获取中都是可选的。其唯一接受的值为 1。省略时,
usage 属性将被省略,服务器不会执行使用情况查询。
使用情况摘要
usage.count 是其所选当前索引源引用该媒体项目的 EmDash 不同活跃内容行或语言环境的数量。同一内容条目的重复引用和多个源变体只计算一次。回收站中的内容不计入。
数字计数可能会揭示类似草稿的内容。仅在会话用户具有 content:read_drafts 时,或当 API 令牌具有 admin 作用域且其关联用户也具有该权限时才返回。其他媒体读者收到 usage.count: null;这是成功的已编辑响应,不是错误。
每个请求的摘要包含当前所有已注册内容集合的聚合覆盖率:
| 状态 | 含义 |
|---|---|
complete | 每个已注册集合都有当前的、已完成的使用情况覆盖 |
never | 没有已注册集合完成过初始使用情况修复 |
running | 使用情况修复当前正在运行 |
partial | 覆盖率混合或仅注册范围的部分被索引 |
failed | 在注册范围内覆盖率失败 |
stale | 索引覆盖率已过时 |
unknown | 存储的覆盖率包含此版本无法识别的状态 |
只有 complete 支持在下面描述的 EmDash 管理字段内的有范围的完全零声明。具有任何其他状态的计数是索引投影,可能过度报告或不足报告。即使是完整的结果在并发写入期间也是参考性的;使用情况读取不是事务锁,不得用作删除保证。
获取媒体使用情况详情
GET /_emdash/api/media/:id/usage?limit=50&cursor=...
此端点需要 media:read 和 content:read_drafts。令牌认证的调用者还需要 admin 作用域;令牌作用域不会绕过关联用户的权限。
limit 控制每页的内容条目组,范围为 1 到 100(默认:50)。分页永远不会拆分返回条目组的源或出现次数。
{
"data": {
"items": [
{
"collection": "posts",
"contentId": "01CONTENT...",
"title": "Launch notes",
"slug": "launch-notes",
"locale": "en",
"status": "published",
"scheduledAt": null,
"deletedAt": null,
"sources": [
{
"variant": "columns",
"occurrences": [
{
"fieldSlug": "hero",
"fieldPath": "hero",
"occurrenceIndex": 0,
"referenceType": "image_field"
}
]
}
]
}
],
"nextCursor": "eyJvcmRlclZhbHVlIjoicG9zdHMiLCJpZCI6IjAxLi4uIn0",
"coverage": {
"scope": "all_content_collections",
"status": "complete"
}
}
}
授权详情包括活跃和已删除的条目。非空的 deletedAt 标识回收站条目。源为 columns 或 draft_overlay;出现次数标识支持的字段和路径,而不暴露内部索引元数据。
媒体使用情况涵盖 EmDash 内容集合管理的顶级图片和文件字段、重复器图片字段和 Portable Text 图片块中的本地媒体引用。它不扫描自定义代码、渲染的 HTML、设置、菜单、小部件、插件私有数据、外部站点或仅提供商的资产。
创建媒体
POST /_emdash/api/media
Content-Type: application/json
请求体
{
"filename": "photo.jpg",
"mimeType": "image/jpeg",
"size": 102400,
"width": 1920,
"height": 1080,
"storageKey": "uploads/photo.jpg"
}
更新媒体
PUT /_emdash/api/media/:id
Content-Type: application/json
请求体
{
"alt": "Photo description",
"caption": "Photo caption"
}
删除媒体
DELETE /_emdash/api/media/:id
修复媒体使用情况
POST /_emdash/api/admin/media-usage/repair
Content-Type: application/json
X-EmDash-Request: 1
修复一个集合或所有内容集合的内容媒体使用情况索引。这是管理员/运维端点:会话认证的调用者需要 schema:manage,Bearer 令牌必须具有 admin 作用域,因为路由在 /_emdash/api/admin 下。
全内容修复在当前版本中同步顺序执行。在大型站点上可能代价高昂,因此调用者应有意触发并等待响应。
请求体
修复一个集合:
{
"scope": "collection",
"collection": "posts"
}
修复所有内容集合:
{
"scope": "all"
}
请求体是必需的。无效的 slug、未知的请求键、缺少 scope 和无请求体的请求返回 400,而不是默认修复所有内容。
响应
当修复调用产生结构化结果时,端点返回 200。检查 data.status:failed 和 stale 是修复域状态,不是传输错误。
{
"data": {
"status": "complete",
"indexedSourceCount": 12,
"failedSourceCount": 0,
"skippedSourceCount": 0,
"deletedSourceCount": 1,
"collections": [
{
"collection": "posts",
"status": "complete",
"indexedSourceCount": 12,
"failedSourceCount": 0,
"skippedSourceCount": 0,
"deletedSourceCount": 1,
"lastErrorCode": null,
"startedAt": "2026-07-07T12:00:00.000Z",
"completedAt": "2026-07-07T12:00:01.000Z"
}
]
}
}
顶级响应字段:
| 字段 | 类型 | 描述 |
|---|---|---|
status | complete | partial | failed | stale | 聚合修复状态 |
indexedSourceCount | number | 修复期间索引的源 |
failedSourceCount | number | 修复期间失败的源 |
skippedSourceCount | number | 跳过的源,包括过时冲突 |
deletedSourceCount | number | 修复期间删除的过时使用行 |
collections | array | 每个集合的修复摘要 |
集合摘要字段:
| 字段 | 类型 | 描述 |
|---|---|---|
collection | string | 集合标识符 |
status | complete | partial | failed | stale | 集合修复状态 |
indexedSourceCount | number | 此集合索引的源 |
failedSourceCount | number | 此集合失败的源 |
skippedSourceCount | number | 此集合跳过的源 |
deletedSourceCount | number | 此集合删除的过时使用行 |
lastErrorCode | string | null | 最后的集合修复错误(如果可用) |
startedAt | string | 修复开始时间 |
completedAt | string | null | 完成时间,过时结果为 null |
未知集合返回 200,带有 data.status: "failed" 和每个集合的 lastErrorCode(如 COLLECTION_NOT_FOUND)。传输错误仍使用标准错误信封,包括 400、401、403、413 和 500。
获取媒体文件
GET /_emdash/api/media/file/:key
提供实际的文件内容。仅用于本地存储。
修订版端点
列出修订版
GET /_emdash/api/content/:collection/:entryId/revisions
参数
| 参数 | 类型 | 描述 |
|---|---|---|
limit | number | 返回的最大修订版数(默认:50) |
响应
{
"success": true,
"data": {
"items": [
{
"id": "01HXK5MZSN...",
"collection": "posts",
"entryId": "01HXK5MZSN...",
"data": { ... },
"createdAt": "2025-01-24T12:00:00Z"
}
],
"total": 5
}
}
获取修订版
GET /_emdash/api/revisions/:revisionId
恢复修订版
POST /_emdash/api/revisions/:revisionId/restore
将内容恢复到此修订版的状态并创建新的修订版。
架构端点
列出集合
GET /_emdash/api/schema/collections
响应
{
"success": true,
"data": {
"items": [
{
"id": "01HXK5MZSN...",
"slug": "posts",
"label": "Posts",
"labelSingular": "Post",
"supports": ["drafts", "revisions", "preview"]
}
]
}
}
获取集合
GET /_emdash/api/schema/collections/:slug
参数
| 参数 | 类型 | 描述 |
|---|---|---|
includeFields | boolean | 包含字段定义(查询) |
创建集合
POST /_emdash/api/schema/collections
Content-Type: application/json
请求体
{
"slug": "products",
"label": "Products",
"labelSingular": "Product",
"description": "Product catalog",
"supports": ["drafts", "revisions"]
}
更新集合
PUT /_emdash/api/schema/collections/:slug
Content-Type: application/json
删除集合
DELETE /_emdash/api/schema/collections/:slug
参数
| 参数 | 类型 | 描述 |
|---|---|---|
force | boolean | 即使集合有内容也删除(查询) |
列出字段
GET /_emdash/api/schema/collections/:slug/fields
创建字段
POST /_emdash/api/schema/collections/:slug/fields
Content-Type: application/json
请求体
{
"slug": "price",
"label": "Price",
"type": "number",
"required": true,
"validation": {
"min": 0
}
}
更新字段
PUT /_emdash/api/schema/collections/:collectionSlug/fields/:fieldSlug
Content-Type: application/json
删除字段
DELETE /_emdash/api/schema/collections/:collectionSlug/fields/:fieldSlug
重新排序字段
POST /_emdash/api/schema/collections/:slug/fields/reorder
Content-Type: application/json
请求体
{
"fieldSlugs": ["title", "content", "author", "publishedAt"]
}
架构导出
导出架构(JSON)
GET /_emdash/api/schema
Accept: application/json
导出架构(TypeScript)
GET /_emdash/api/schema?format=typescript
Accept: text/typescript
返回所有集合的 TypeScript 接口。
插件端点
列出插件
GET /_emdash/api/admin/plugins
获取插件
GET /_emdash/api/admin/plugins/:id
启用插件
POST /_emdash/api/admin/plugins/:id/enable
禁用插件
POST /_emdash/api/admin/plugins/:id/disable
错误代码
| 代码 | HTTP 状态 | 描述 |
|---|---|---|
NOT_FOUND | 404 | 资源未找到 |
VALIDATION_ERROR | 400 | 无效的输入数据 |
UNAUTHORIZED | 401 | 令牌缺失或无效 |
FORBIDDEN | 403 | 权限不足 |
CONTENT_LIST_ERROR | 500 | 列出内容失败 |
CONTENT_CREATE_ERROR | 500 | 创建内容失败 |
CONTENT_UPDATE_ERROR | 500 | 更新内容失败 |
CONTENT_DELETE_ERROR | 500 | 删除内容失败 |
MEDIA_LIST_ERROR | 500 | 列出媒体失败 |
MEDIA_CREATE_ERROR | 500 | 创建媒体失败 |
SCHEMA_CREATE_ERROR | 500 | 架构操作失败 |
SLUG_CONFLICT | 409 | Slug 已存在 |
RESERVED_SLUG | 400 | Slug 被保留 |
搜索端点
全局搜索
GET /_emdash/api/search?q=hello+world
参数
| 参数 | 类型 | 描述 |
|---|---|---|
q | string | 搜索查询(必填) |
collections | string | 逗号分隔的集合标识符 |
status | string | 按状态筛选(默认:published) |
limit | number | 最大结果数(默认:20) |
cursor | string | 分页游标 |
响应
{
"success": true,
"data": {
"items": [
{
"collection": "posts",
"id": "01HXK5MZSN...",
"slug": "hello-world",
"locale": "en",
"title": "Hello World",
"snippet": "...this is a <mark>hello</mark> <mark>world</mark> example...",
"score": 0.95
}
],
"nextCursor": "eyJvZmZzZXQiOjIwfQ"
}
}
搜索建议
GET /_emdash/api/search/suggest?q=hel&limit=5
返回用于自动补全的前缀匹配标题。
重建搜索索引
POST /_emdash/api/search/rebuild
为所有或特定集合重建 FTS 索引。
搜索统计
GET /_emdash/api/search/stats
返回每个集合的索引文档计数。
区块端点
列出区块
GET /_emdash/api/sections
GET /_emdash/api/sections?source=theme
GET /_emdash/api/sections?search=newsletter
获取区块
GET /_emdash/api/sections/:slug
创建区块
POST /_emdash/api/sections
Content-Type: application/json
{
"slug": "my-section",
"title": "My Section",
"keywords": ["keyword1"],
"content": [...]
}
更新区块
PUT /_emdash/api/sections/:slug
删除区块
DELETE /_emdash/api/sections/:slug
设置端点
获取所有设置
GET /_emdash/api/settings
更新设置
POST /_emdash/api/settings
Content-Type: application/json
{
"siteTitle": "My Site",
"tagline": "A great site",
"postsPerPage": 10
}
菜单端点
列出菜单
GET /_emdash/api/menus
获取菜单
GET /_emdash/api/menus/:name
创建菜单
POST /_emdash/api/menus
Content-Type: application/json
{
"name": "main",
"label": "Main Navigation",
"items": []
}
更新菜单
PUT /_emdash/api/menus/:name
删除菜单
DELETE /_emdash/api/menus/:name
添加菜单项
POST /_emdash/api/menus/:name/items
Content-Type: application/json
{
"label": "About",
"url": "/about",
"position": 0
}
重新排序菜单项
POST /_emdash/api/menus/:name/reorder
Content-Type: application/json
{
"itemIds": ["item_1", "item_2", "item_3"]
}
分类法端点
列出分类法定义
GET /_emdash/api/taxonomies
创建分类法
POST /_emdash/api/taxonomies
Content-Type: application/json
{
"name": "categories",
"label": "Categories",
"hierarchical": true,
"collections": ["posts"]
}
列出术语
GET /_emdash/api/taxonomies/:name/terms
创建术语
POST /_emdash/api/taxonomies/:name/terms
Content-Type: application/json
{
"slug": "tutorials",
"label": "Tutorials",
"parentId": "term_abc",
"description": "How-to guides"
}
更新术语
PUT /_emdash/api/taxonomies/:name/terms/:slug
删除术语
DELETE /_emdash/api/taxonomies/:name/terms/:slug
设置条目术语
POST /_emdash/api/content/:collection/:id/terms/:taxonomy
Content-Type: application/json
{
"termIds": ["term_news", "term_featured"]
}
小部件区域端点
列出小部件区域
GET /_emdash/api/widget-areas
获取小部件区域
GET /_emdash/api/widget-areas/:name
创建小部件区域
POST /_emdash/api/widget-areas
Content-Type: application/json
{
"name": "sidebar",
"label": "Main Sidebar",
"description": "Appears on posts"
}
删除小部件区域
DELETE /_emdash/api/widget-areas/:name
添加小部件
POST /_emdash/api/widget-areas/:name/widgets
Content-Type: application/json
{
"type": "content",
"title": "About",
"content": [...]
}
更新小部件
PUT /_emdash/api/widget-areas/:name/widgets/:id
删除小部件
DELETE /_emdash/api/widget-areas/:name/widgets/:id
重新排序小部件
POST /_emdash/api/widget-areas/:name/reorder
Content-Type: application/json
{
"widgetIds": ["widget_1", "widget_2", "widget_3"]
}
用户管理端点
列出用户
GET /_emdash/api/admin/users
GET /_emdash/api/admin/users?role=40
GET /_emdash/api/admin/users?search=john
获取用户
GET /_emdash/api/admin/users/:id
更新用户
PUT /_emdash/api/admin/users/:id
Content-Type: application/json
{
"name": "John Doe",
"role": 40
}
启用用户
POST /_emdash/api/admin/users/:id/enable
禁用用户
POST /_emdash/api/admin/users/:id/disable
认证端点
设置状态
GET /_emdash/api/setup/status
返回设置是否完成以及是否存在用户。
Passkey 登录
POST /_emdash/api/auth/passkey/options
获取 WebAuthn 认证选项。
POST /_emdash/api/auth/passkey/verify
Content-Type: application/json
{
"id": "credential-id",
"rawId": "...",
"response": {...},
"type": "public-key"
}
验证 Passkey 并创建会话。
魔法链接
POST /_emdash/api/auth/magic-link/send
Content-Type: application/json
{
"email": "[email protected]"
}
GET /_emdash/api/auth/magic-link/verify?token=xxx
登出
POST /_emdash/api/auth/logout
当前用户
GET /_emdash/api/auth/me
邀请用户
POST /_emdash/api/auth/invite
Content-Type: application/json
{
"email": "[email protected]",
"role": 30
}
Passkey 管理
GET /_emdash/api/auth/passkey
列出用户的 Passkey。
POST /_emdash/api/auth/passkey/register/options
POST /_emdash/api/auth/passkey/register/verify
注册新 Passkey。
PATCH /_emdash/api/auth/passkey/:id
Content-Type: application/json
{
"name": "MacBook Pro"
}
重命名 Passkey。
DELETE /_emdash/api/auth/passkey/:id
删除 Passkey。
导入端点
分析 WordPress 导出
POST /_emdash/api/import/wordpress/analyze
Content-Type: multipart/form-data
file: <WXR file>
执行 WordPress 导入
POST /_emdash/api/import/wordpress/execute
Content-Type: application/json
{
"analysisId": "...",
"options": {
"includeMedia": true,
"includeTaxonomies": true,
"includeMenus": true
}
}
速率限制
API 端点可能根据部署配置进行速率限制。速率限制时,响应包含:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
CORS
API 支持浏览器请求的 CORS。在部署中配置允许的源。