EmDash 在 /_emdash/api/ 暴露 REST API,用于内容管理、媒体上传和 schema 操作。
身份验证
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 | 集合 slug(路径参数) |
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 | 不透明分页游标 |
page | number | 编号页面,从 1 开始;不能与 cursor 同时使用 |
limit | number | 每页条目数,1 到 100(默认:50) |
mimeType | string | 按一个或多个逗号分隔的 MIME 类型过滤 |
q | string | 不区分大小写的文件名搜索 |
folderId | string | 文件夹 ID,或 unfiled 表示主库 |
includeUsage | 1 | 在每个返回项上包含覆盖范围感知的 usage 摘要 |
省略 folderId 可列出主库和所有文件夹中的媒体。使用 folderId=unfiled 仅列出未分配到文件夹的媒体。编号请求返回 totalCount;游标模式在还有下一页时返回 nextCursor。
响应
{
"success": true,
"data": {
"items": [
{
"id": "01HXK5MZSN...",
"filename": "photo.jpg",
"mimeType": "image/jpeg",
"size": 102400,
"width": 1920,
"height": 1080,
"folderId": null,
"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 在下述支持字段内支持作用域完全零声明。具有任何其他状态的计数是索引投影,可能高报或低报。即使是完整结果在并发写入期间也是参考性的;使用读取不是事务锁,不得用作删除保证。
获取媒体使用详情
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、设置、菜单、小部件、插件私有数据、外部站点或仅供提供商使用的资源。
上传媒体
上传媒体需要 media:upload 权限。Bearer 令牌还需要 media:write 作用域。默认最大文件大小为 50 MB。设置 maxUploadSize 以更改限制。
使用以下上传方法之一:
- 当客户端可以通过 EmDash 发送文件时,使用直接 multipart 上传。
- 使用上传目标流程直接上传到 S3 兼容存储。本地存储和原生 R2 会返回 EmDash 上传 URL。
直接 multipart 上传
在 multipart 请求的 file 字段中发送文件:
curl --request POST \
--header "Authorization: Bearer $EMDASH_TOKEN" \
--header "X-EmDash-Request: 1" \
--form "file=@./photo.jpg;type=image/jpeg" \
https://example.com/_emdash/api/media
curl 会自动将 multipart 边界添加到 Content-Type 头。不要手动设置该头。
该端点还接受以下可选 multipart 字段:
| 字段 | 描述 |
|---|---|
width | 图片宽度(像素) |
height | 图片高度(像素) |
fieldId | 其配置的 MIME 类型允许列表适用于上传的字段 |
thumbnail | 缩小的图片,用于生成低质量图片占位符 |
新上传返回 201 Created,包含存储的媒体项和 folderId: null。如果相同文件已存在,EmDash 返回 200 OK,包含 deduplicated: true、现有媒体项及其当前文件夹分配。直接 multipart 上传立即可用,不使用确认端点。
上传目标流程
上传目标流程在最终确认之前将媒体项保持在 pending 状态。待处理的媒体不会出现在标准媒体列表或媒体库中。
-
请求上传目标
POST /_emdash/api/media/upload-url Authorization: Bearer <token> Content-Type: application/json { "filename": "photo.jpg", "contentType": "image/jpeg", "size": 102400 }filename、contentType和size是必需的。请求还接受以下可选字段:字段 描述 contentHashsha1:加 40 个小写十六进制字符,用于查找匹配项fieldId其配置的 MIME 类型允许列表适用于上传的字段 响应包含文件上传的 URL、方法和头。当存储适配器支持时,
uploadUrl是一个绝对签名 URL。否则,它是一个根相对的 EmDash 端点。{ "success": true, "data": { "uploadUrl": "/_emdash/api/media/01M0AFKJS0RJM3WV69QHAY7YA1/upload", "method": "PUT", "headers": { "Content-Type": "image/jpeg", "X-EmDash-Request": "1" }, "mediaId": "01M0AFKJS0RJM3WV69QHAY7YA1", "storageKey": "01M0AFKJS0K2YF0222NP6ENYWX.jpg", "expiresAt": "2026-08-18T14:05:09.920Z" } }如果
contentHash与具有相同 MIME 类型和大小的现有媒体项匹配,响应包含existing: true、mediaId、storageKey和url,而不是上传目标。使用返回的媒体项并停止。不要上传或确认文件。 -
将文件上传到
uploadUrl从返回的
method和headers开始。将相对uploadUrl解析为 EmDash 站点 URL。对于同源 EmDash 目标,在上传请求中包含 Bearer 令牌。对于另一个源上的签名 URL,仅发送返回的上传头。
同源上传返回以下响应。签名 URL 返回存储提供商的响应。
{ "success": true, "data": { "uploaded": true, "size": 102400 } } -
确认上传
确认操作检查存储的文件并将媒体项从
pending更改为ready。size、width和height是可选的,但 EmDash 在提供时会验证它们。POST /_emdash/api/media/01M0AFKJS0RJM3WV69QHAY7YA1/confirm Authorization: Bearer <token> Content-Type: application/json { "size": 102400, "width": 1920, "height": 1080 }响应包含就绪的媒体项:
{ "success": true, "data": { "item": { "id": "01M0AFKJS0RJM3WV69QHAY7YA1", "filename": "photo.jpg", "status": "ready", "url": "/_emdash/api/media/file/01M0AFKJS0K2YF0222NP6ENYWX.jpg" } } }
上传错误
| 状态 | 代码 | 原因 |
|---|---|---|
400 | NO_FILE | multipart 请求没有 file 字段,或上传体缺失 |
400 | INVALID_TYPE | MIME 类型不允许或与待处理媒体项不匹配 |
400 | VALIDATION_ERROR | 上传元数据缺失、无效或超过配置的大小限制 |
400 | FILE_NOT_FOUND | 确认时找不到上传的对象 |
400 | UPLOAD_SIZE_MISMATCH | 声明的、上传的和确认的大小不匹配 |
400 | INVALID_STATE | 媒体项不是待处理状态 |
404 | NOT_FOUND | 媒体项不存在 |
409 | INVALID_STATE | 待处理的媒体项在确认期间发生更改 |
413 | PAYLOAD_TOO_LARGE | 直接或同源上传太大 |
更新媒体
PUT /_emdash/api/media/:id
Content-Type: application/json
请求体
{
"alt": "Photo description",
"caption": "Photo caption",
"folderId": "01FOLDER..."
}
省略 folderId 保持当前分配不变。设置为 null 或 unfiled 将媒体项返回到主库。分配自己拥有的媒体需要 media:edit_own;分配任何媒体需要 media:edit_any。Bearer 令牌还需要 media:write 作用域。
列出媒体文件夹
GET /_emdash/api/media/folders?limit=50&q=product&cursor=...
| 参数 | 类型 | 描述 |
|---|---|---|
cursor | string | 不透明分页游标 |
limit | number | 每页文件夹数,1 到 100(默认:50) |
q | string | 不区分大小写的部分文件夹名搜索(1–200 个字符) |
按名称顺序返回文件夹,可选 nextCursor。该端点需要 media:read。
获取媒体文件夹
GET /_emdash/api/media/folders/:id
返回请求 ID 的文件夹。该端点需要 media:read,当文件夹不存在时返回 404。
创建媒体文件夹
POST /_emdash/api/media/folders
Content-Type: application/json
{
"name": "Product photos"
}
返回 201 Created,包含创建的文件夹:
{
"success": true,
"data": {
"item": {
"id": "01HXK5MZSN...",
"name": "Product photos"
}
}
}
文件夹名称会被修剪,必须包含 1 到 200 个字符。名称在 Unicode 规范化和小写转换后进行比较,因此 Photos、photos 和 PHOTOS 被视为重复。
创建、重命名和删除文件夹需要 media:edit_any。Bearer 令牌还需要 media:write 作用域。
重命名媒体文件夹
PUT /_emdash/api/media/folders/:id
Content-Type: application/json
{
"name": "Published product photos"
}
返回 200 OK,包含更新后的文件夹,响应格式与创建相同。
删除媒体文件夹
DELETE /_emdash/api/media/folders/:id
返回 200 OK,响应 data 中包含 { "deleted": true }。
删除文件夹会将其中的媒体返回到主库。它不会删除媒体、更改媒体 ID 或 URL,也不会更改媒体使用记录。
文件夹错误
| 状态 | 代码 | 原因 |
|---|---|---|
400 | INVALID_CURSOR | 文件夹列表游标无效 |
400 | VALIDATION_ERROR | 文件夹名称、文件夹 ID 或列表参数无效 |
404 | NOT_FOUND | 文件夹或文件夹分配目标不存在 |
409 | CONFLICT | 已有文件夹具有相同的规范化名称 |
删除媒体
DELETE /_emdash/api/media/:id
启用媒体使用跟踪
关闭了媒体使用跟踪的站点需要开启一次。在 EmDash 准备每个集合时,暂停对数据库的直接写入。EmDash 在此步骤中临时阻止自身的内容和 schema 写入。
两个端点都需要 schema:manage。Bearer 令牌还需要 admin 作用域。
有关管理员操作步骤,请参阅开启媒体使用跟踪。以下端点为 API 操作者提供相同的流程。
检查当前状态
GET /_emdash/api/admin/media-usage/activation
此请求不会更改任何内容。它返回以下状态之一:
expanded:媒体使用跟踪已关闭。activating:EmDash 正在准备站点的集合。active:EmDash 跟踪内容中媒体引用的更改。
状态响应不包含内部锁数据或原始数据库错误。
开始激活
POST /_emdash/api/admin/media-usage/activation
Content-Type: application/json
X-EmDash-Request: 1
{
"writersDrained": true
}
该请求最多准备一个集合。成功后,通过下面的进度端点推进设置和历史索引。
在应用程序和直接数据库写入停止且所有正在进行的写入完成后,将 writersDrained 设置为 true。
使用 API 启用跟踪
- 停止所有直接数据库写入者。等待正在进行的写入完成。EmDash 在设置期间会隔离自身的写入。
- 调用激活
GET端点检查当前状态。 - 使用
writersDrained: true调用一次激活POST端点。 - 串行调用进度
POST端点,遵循nextRequestInMs,直到激活变为active。 - 激活变为
active后恢复直接数据库写入。 - 继续进度请求,直到历史索引报告
ready且nextRequestInMs为null。
如果 POST 超时或返回 409 或 500,在决定操作之前先调用 GET。如果状态仍为 activating 且没有 lastErrorCode,则另一个请求可能拥有当前批次。如果设置了 lastErrorCode,保持写入停止,检查应用程序日志,修复问题,然后发送一个确认的 POST 进行重试。不要编辑 EmDash 的内部数据库表。
当状态为 active 时,EmDash 跟踪内容中媒体引用的更改。现有内容可能仍需要进度请求才能完成历史索引。
检查历史索引进度
GET /_emdash/api/admin/media-usage/progress
激活变为 active 后,此端点返回 indexing、ready 或 needs_attention,以及就绪和总计的当前内容类型数。该端点不检查内容行或返回工作项详情。需要 schema:manage;bearer 令牌还需要 admin 作用域。
推进设置和历史索引
POST /_emdash/api/admin/media-usage/progress
X-EmDash-Request: 1
该请求没有请求体。它运行一个有界维护步骤,并在该步骤后返回存储的激活和进度状态。
{
"success": true,
"data": {
"activation": {
"state": "active",
"collectionCursor": null,
"attemptCount": 2,
"drainConfirmedAt": "2026-08-24T12:00:00.000Z",
"lastAttemptedAt": "2026-08-24T12:00:01.000Z",
"lastErrorCode": null,
"leaseExpiresAt": null,
"activatedAt": "2026-08-24T12:00:01.000Z",
"updatedAt": "2026-08-24T12:00:02.000Z"
},
"progress": {
"status": "indexing",
"readyCollections": 1,
"totalCollections": 2
},
"nextRequestInMs": 0
}
}
progress 在激活变为 active 之前为 null。nextRequestInMs 为 0 表示立即后续请求,30000 表示延迟重试,null 表示服务器不知道有后续。一次只发送一个进度请求,并等待返回的延迟时间。
关闭客户端不会丢弃已完成的工作,但会停止未来的请求。要恢复,先读取激活状态,当激活为 active 时读取进度,然后继续进度请求。在收到模糊响应后,先执行相同的读取再重试。
列出媒体使用工作
GET /_emdash/api/admin/media-usage/work?collection=posts&state=failed&limit=50&cursor=...
返回一个当前集合的持久条目索引工作的有界页面。该端点需要 schema:manage;bearer 令牌还需要 admin 作用域。
collection 是必需的。state 可选地过滤 pending、retry、leased 或 failed 工作。limit 默认为 50,上限为 100。cursor 是不透明的,来自上一页的 nextCursor。该端点不计算精确的积压计数。
{
"success": true,
"data": {
"items": [
{
"collectionId": "01COLLECTION...",
"collectionSlug": "posts",
"contentId": "01CONTENT...",
"state": "failed",
"attemptCount": 5,
"nextAttemptAt": "2026-08-07T12:00:00.000Z",
"leaseExpiresAt": null,
"lastAttemptedAt": "2026-08-07T11:45:00.000Z",
"lastErrorCode": "MEDIA_USAGE_PROCESSING_FAILED",
"updatedAt": "2026-08-07T11:45:00.000Z"
}
],
"nextCursor": "eyJvcmRlclZhbHVlIjoiLi4uIn0"
}
}
响应省略工作版本、租约令牌、原始数据库错误、索引内容、媒体引用和精确计数。
重试媒体使用工作
POST /_emdash/api/admin/media-usage/work/retry
Content-Type: application/json
X-EmDash-Request: 1
幂等地重新打开或创建一个持久条目任务。它与列表端点具有相同的授权要求。
{
"collectionId": "01COLLECTION...",
"contentId": "01CONTENT..."
}
成功响应返回 changed 和当前的待处理项。changed: false 表示任务已经是待处理状态。未过期的工作者租约返回 409 WORK_LEASE_ACTIVE 和 details.leaseExpiresAt;并发变更返回 409 WORK_CHANGED。两种冲突都不会替换较新的工作或暴露其租约令牌。
列表仅返回已知的持久工作。重试可以为已提供的身份在活跃集合中创建工作,即使不存在工作行,但它不扫描历史差距。在导入或直接数据库写入后使用集合范围的媒体使用修复。
失败的任务保持可见且可手动重试。needs_attention 进度状态会停止媒体使用跟踪设置页面的自动请求,直到解决底层故障。
恢复集合删除
GET /_emdash/api/admin/media-usage/collection-deletions?state=failed&limit=50&cursor=...
返回持久集合删除工作的有界页面。列表默认为失败的工作;limit 默认为 50,上限为 100。项包括不可变集合 ID、slug、阶段、尝试次数、资格/租约时间戳、稳定错误代码和更新时间。租约令牌、原始数据库错误、内容、媒体引用和精确积压计数从不返回。
POST /_emdash/api/admin/media-usage/collection-deletions/retry
Content-Type: application/json
X-EmDash-Request: 1
{ "collectionId": "01COLLECTION..." }
重试会重新打开失败的、正在重试的或租约已过期的工作,而不更改其阶段。活跃租约返回 409 WORK_LEASE_ACTIVE;并发状态更改返回 409 WORK_CHANGED。两个路由都需要 schema:manage,bearer 令牌还需要 admin 作用域。它们仅恢复内部索引清理,从不删除媒体资源。
修复媒体使用
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 | 集合 slug |
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
将内容恢复到此修订版本的状态并创建新的修订版本。
Schema 端点
列出集合
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"]
}
Schema 导出
导出 Schema(JSON)
GET /_emdash/api/schema
Accept: application/json
导出 Schema(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 | 更新内容失败 |
SAVE_REJECTED | 422 | 保存被插件钩子拒绝 |
CONTENT_HOOK_ERROR | 500 | 保存期间插件钩子失败 |
CONTENT_DELETE_ERROR | 500 | 删除内容失败 |
MEDIA_LIST_ERROR | 500 | 列出媒体失败 |
MEDIA_CREATE_ERROR | 500 | 创建媒体失败 |
SCHEMA_CREATE_ERROR | 500 | Schema 操作失败 |
SLUG_CONFLICT | 409 | Slug 已存在 |
RESERVED_SLUG | 400 | Slug 是保留的 |
搜索端点
全局搜索
GET /_emdash/api/search?q=hello+world
参数
| 参数 | 类型 | 描述 |
|---|---|---|
q | string | 搜索查询(必需) |
collections | string | 逗号分隔的集合 slug |
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/enable
Content-Type: application/json
{
"collection": "posts",
"enabled": true,
"tokenize": "trigram",
"weights": {
"title": 10,
"content": 1
}
}
可选的 tokenize 字段控制 SQLite FTS5 如何索引集合。在已启用的集合上更改它会重建并重新填充该集合的搜索索引。
| 值 | 适用场景 |
|---|---|
porter unicode61 | 默认。受益于 Porter 词干提取的英语内容,例如匹配相关词形。Porter 词干提取是英语特定的。 |
unicode61 | 使用单词分隔符但不应使用英语词干提取的语言。 |
trigram | 文本不以空格分隔的语言,包括日语、中文、泰语、高棉语、老挝语和缅甸语,或需要子串匹配时。短于三个 Unicode 字符的查询不返回匹配项。 |
在没有存储分词器的集合上省略 tokenize 使用 porter unicode61。禁用搜索会保留配置的分词器,供下次启用操作使用。
重建搜索索引
POST /_emdash/api/search/rebuild
Content-Type: application/json
{
"collection": "posts"
}
使用其存储的分词器和字段权重为指定集合重建 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": "footer",
"label": "Footer Navigation"
}
更新菜单
PUT /_emdash/api/menus/:name
删除菜单
DELETE /_emdash/api/menus/:name
添加菜单项
POST /_emdash/api/menus/:name/items
Content-Type: application/json
{
"type": "page",
"referenceCollection": "pages",
"referenceId": "page_about",
"label": "About Us"
}
重新排序菜单项
POST /_emdash/api/menus/:name/reorder
Content-Type: application/json
{
"items": [
{ "id": "item_1", "parentId": null, "sortOrder": 0 },
{ "id": "item_2", "parentId": null, "sortOrder": 1 },
{ "id": "item_3", "parentId": "item_2", "sortOrder": 0 }
]
}
分类法端点
列出分类法定义
GET /_emdash/api/taxonomies
获取分类法
GET /_emdash/api/taxonomies/:name
分类法每个语言环境有一个定义。locale 选择返回哪一个。省略时,EmDash 返回站点默认语言环境的定义,在默认语言环境没有定义时回退到最小语言环境代码。该端点需要 taxonomies:read。
参数
| 参数 | 类型 | 描述 |
|---|---|---|
name | string | 分类法名称(路径参数) |
locale | string | 定义的语言环境(查询参数) |
响应
{
"success": true,
"data": {
"taxonomy": {
"id": "01HXK5MZSN...",
"name": "genre",
"label": "Genres",
"labelSingular": "Genre",
"hierarchical": true,
"collections": ["books", "movies"],
"locale": "en",
"translationGroup": "01HXK5MZSN..."
}
}
}
collections 仅列出仍然存在的集合。在添加到分类法后被删除的集合会从响应中过滤掉,但保留在存储中,因此重新创建该集合会恢复链接。
分类法的每个语言环境共享一个 translationGroup。未翻译的分类法的 id 就在那里,如上所示。
创建分类法
POST /_emdash/api/taxonomies
Content-Type: application/json
{
"name": "genre",
"label": "Genres",
"labelSingular": "Genre",
"hierarchical": true,
"collections": ["books", "movies"]
}
更新分类法
PUT /_emdash/api/taxonomies/:name
Content-Type: application/json
{
"label": "Categories",
"labelSingular": "Category",
"hierarchical": true,
"collections": ["books"]
}
每个字段都是可选的,省略的字段保留其存储值。发送 "labelSingular": null 清除它。命名不存在的集合返回 VALIDATION_ERROR 且不写入任何内容。响应是更新后的定义,格式与获取分类法相同。该端点需要 taxonomies:manage。
请求写入单个语言环境的定义,由 locale 选择。在翻译的分类法上明确传递它:如果不传,写入会落在语言环境代码最小的定义上。寻址没有定义的语言环境返回 NOT_FOUND — 与获取分类法不同,该端点从不回退到其他语言环境的行。
不要在请求体中发送 name 或 locale。两者都标识正在写入的定义而不是要更改的值,因此请求体会以 VALIDATION_ERROR 拒绝它们而不是忽略它们。分类法无法重命名,因为其术语以 name 为键。
删除分类法
DELETE /_emdash/api/taxonomies/:name
删除所有语言环境中的分类法:每个语言环境的定义、该名称下的每个术语,以及这些术语到内容的每个分配。内容条目本身不会被删除;它们会失去术语分配。
没有 locale 参数,当分类法仍有术语时 EmDash 不会拒绝请求。该端点需要 taxonomies:manage。
响应
{
"success": true,
"data": { "deleted": true }
}
列出分类法翻译
GET /_emdash/api/taxonomies/:name/translations
列出分类法定义已翻译到的每个语言环境。分类法的任何语言环境都返回相同的列表,因此 locale 仅选择哪个定义解析组。该端点需要 taxonomies:read。
响应
{
"success": true,
"data": {
"translationGroup": "01HXK5MZSN...",
"translations": [
{ "id": "01HXK5MZSN...", "name": "genre", "label": "Genres", "locale": "en" },
{ "id": "01HXK6P2QT...", "name": "genre", "label": "Géneros", "locale": "es" }
]
}
}
要添加语言环境,使用相同的 name、新的 locale 和设置为此列表中某个定义 id 的 translationOf 发布到创建分类法。
列出术语
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/taxonomies/:name/reorder
Content-Type: application/json
{
"parentId": "term_abc",
"ids": ["term_news", "term_featured"]
}
设置一个兄弟组的顺序。parentId 指定其子项正在排序的父项;省略它(或发送 null)表示顶级,对于扁平分类法即所有术语。重新排序从不更改术语的父项 — 使用更新术语来实现。
ids 可以是组的子集:你列出的术语在它们已占据的位置内进行排列,其他每个成员保持其位置。当一个语言环境不渲染整个组时这很重要,这意味着过时的列表不能埋没它遗漏的术语。组外的 id 会被以 REORDER_MISMATCH 拒绝,一次最多可以发送 100 个 id。
因为你遗漏的术语保持其绝对位置,部分列表中的一步移动可以使术语越过该列表未包含的兄弟。如果 [A, B, C] 是完整组,你发送 ["C", "A"] — 因为 B 没有翻译到你正在使用的语言环境 — 结果是 [C, B, A]:A 和 C 按要求交换,显示 B 的列表看到 A 移动了两个位置而不是一个。
没有 locale 参数。一个术语在翻译到的每个语言环境中保持一个位置,因此 id 可以是术语 id 或翻译组,在一个语言环境中排序分类法就是在所有语言环境中排序。需要每个语言环境不同顺序的站点应使用单独的分类法。
设置条目术语
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。在部署中配置允许的来源。