REST API 參考

本頁內容

EmDash 在 /_emdash/api/ 下公開其支援的應用程式設計介面(API)。請使用產生的 OpenAPI 3.1 文件查看請求參數、請求主體、回應 schema、狀態碼以及用戶端產生資訊:

GET /_emdash/api/openapi.json

該文件由 API 使用的同一套 Zod schema 產生,並反映所設定的最大媒體上傳大小。

公開契約邊界

OpenAPI 文件是所支援 REST API 的邊界。EmDash 還有管理介面與協定工作流程的路由。原始碼中存在但未出現在 OpenAPI 中的路由,對外部用戶端而言不是受支援的 REST 操作。

這一區分適用於備份、署名管理、關係遍歷、外掛管理、安裝、匯入與認證等路由。備份請參閱備份指南;受支援的署名管理請使用 MCP 署名工具。OAuth 端點是協定端點,請透過 MCP OAuth 章節中的詮釋資料探索,而不要將其當作應用程式 REST 端點。

認證與授權

大多數操作接受 EmDash 工作階段 cookie 或 Bearer 權杖。請在 Authorization 標頭中傳送個人存取權杖或 OAuth 存取權杖:

Authorization: Bearer $EMDASH_TOKEN

Bearer 權杖受其 scope 與關聯使用者角色限制。工作階段請求使用該使用者的角色。授權模型請參閱使用者角色與權杖 scope。

GET 和 POST /_emdash/api/comments/{collection}/{contentId} 是公開的。GET 操作傳回已核准的評論;POST 操作提交一則評論以供審核。其餘的評論審核操作都需要認證。

跨站請求偽造防護

對於以工作階段 cookie 認證、且會變更狀態的請求,請包含此標頭:

X-EmDash-Request: 1

Bearer 權杖請求不需要此標頭,因為它們不使用瀏覽器的環境憑證。瀏覽器對公開寫入操作發出的請求,必須傳送此標頭,或其 Origin 必須與 EmDash 站點的公開來源或請求來源相符。

回應封裝

成功的 JSON 回應會將 success 設為 true,並把該操作特有的結果放在 data 中:

{
	"success": true,
	"data": {
		"items": []
	}
}

錯誤會將 success 設為 false,並包含穩定、可供機器讀取的錯誤碼和一則訊息。部分錯誤還會包含結構化的 details:

{
	"success": false,
	"error": {
		"code": "NOT_FOUND",
		"message": "Content item not found"
	}
}

請使用每個 OpenAPI 操作上的狀態碼和錯誤 schema。常見狀態有:400 表示輸入無效,401 表示缺少或無效的憑證,403 表示 scope 或權限不足,404 表示資源不存在,409 表示狀態衝突,413 表示上傳過大,422 表示儲存被外掛拒絕,500 表示內部故障。

端點清單

操作 ID 在產生的契約中保持穩定,OpenAPI 用戶端產生器通常把它用作方法名稱。以下清單已與產生的 OpenAPI 文件核對。

內容

方法路徑操作摘要
GET/_emdash/api/content/{collection}listContentList content items
POST/_emdash/api/content/{collection}createContentCreate a content item
GET/_emdash/api/content/{collection}/{id}getContentGet a content item
PUT/_emdash/api/content/{collection}/{id}updateContentUpdate a content item
DELETE/_emdash/api/content/{collection}/{id}deleteContentDelete a content item (soft delete)
POST/_emdash/api/content/{collection}/{id}/publishpublishContentPublish a content item
POST/_emdash/api/content/{collection}/{id}/unpublishunpublishContentUnpublish a content item
POST/_emdash/api/content/{collection}/{id}/schedulescheduleContentSchedule content for future publishing
DELETE/_emdash/api/content/{collection}/{id}/scheduleunscheduleContentCancel scheduled publishing
POST/_emdash/api/content/{collection}/{id}/duplicateduplicateContentDuplicate a content item
POST/_emdash/api/content/{collection}/{id}/restorerestoreContentRestore a content item from trash
DELETE/_emdash/api/content/{collection}/{id}/permanentpermanentDeleteContentPermanently delete a content item
GET/_emdash/api/content/{collection}/{id}/comparecompareContentCompare live and draft revisions
POST/_emdash/api/content/{collection}/{id}/discard-draftdiscardDraftDiscard draft changes
GET/_emdash/api/content/{collection}/{id}/lockgetEntryLockRead the entry’s edit lock
POST/_emdash/api/content/{collection}/{id}/lockacquireEntryLockTake or refresh the entry’s edit lock
DELETE/_emdash/api/content/{collection}/{id}/lockreleaseEntryLockRelease the caller’s edit lock
GET/_emdash/api/content/{collection}/{id}/translationsgetContentTranslationsGet translations for a content item
GET/_emdash/api/content/{collection}/{id}/terms/{taxonomy}getContentTermsGet taxonomy terms assigned to a content item
POST/_emdash/api/content/{collection}/{id}/terms/{taxonomy}setContentTermsSet taxonomy terms on a content item
GET/_emdash/api/content/{collection}/authorslistContentAuthorsList distinct authors of a collection’s content
GET/_emdash/api/content/{collection}/trashlistTrashedContentList trashed content items

媒體

方法路徑操作摘要
GET/_emdash/api/medialistMediaList media items
POST/_emdash/api/mediauploadMediaUpload a media item
GET/_emdash/api/media/folderslistMediaFoldersList media folders
POST/_emdash/api/media/folderscreateMediaFolderCreate a media folder
GET/_emdash/api/media/folders/{id}getMediaFolderGet a media folder
PUT/_emdash/api/media/folders/{id}updateMediaFolderUpdate a media folder
DELETE/_emdash/api/media/folders/{id}deleteMediaFolderDelete a media folder
GET/_emdash/api/media/{id}getMediaGet a media item
PUT/_emdash/api/media/{id}updateMediaUpdate media metadata
DELETE/_emdash/api/media/{id}deleteMediaDelete a media item
GET/_emdash/api/media/{id}/usagegetMediaUsageGet media usage details
PUT/_emdash/api/media/{id}/replacereplaceMediaImageReplace a media image
POST/_emdash/api/admin/media-usage/repairrepairMediaUsageRepair media usage indexes
GET/_emdash/api/admin/media-usage/progressgetMediaUsageProgressGet media usage indexing progress
POST/_emdash/api/admin/media-usage/progressadvanceMediaUsageProgressAdvance media usage indexing
GET/_emdash/api/admin/media-usage/worklistMediaUsageWorkList durable media usage work
GET/_emdash/api/admin/media-usage/activationgetMediaUsageActivationGet media usage activation status
POST/_emdash/api/admin/media-usage/activationadvanceMediaUsageActivationAdvance media usage activation
POST/_emdash/api/admin/media-usage/work/retryretryMediaUsageWorkRetry one durable media usage job
GET/_emdash/api/admin/media-usage/collection-deletionslistMediaUsageCollectionDeletionsList durable collection deletions
POST/_emdash/api/admin/media-usage/collection-deletions/retryretryMediaUsageCollectionDeletionRetry one collection deletion
POST/_emdash/api/media/upload-urlgetMediaUploadUrlGet a media upload target
POST/_emdash/api/media/{id}/confirmconfirmMediaUploadConfirm a media upload
PUT/_emdash/api/media/{id}/uploaduploadPendingMediaUpload a pending media file through EmDash

Schema

方法路徑操作摘要
GET/_emdash/api/schema/block-typeslistBlockTypesList block types
POST/_emdash/api/schema/block-typescreateBlockTypeCreate a block type
GET/_emdash/api/schema/block-types/{slug}getBlockTypeGet a block type
PUT/_emdash/api/schema/block-types/{slug}updateBlockTypeUpdate a block type
POST/_emdash/api/schema/block-types/{slug}/versions/{version}/activateactivateBlockTypeVersionActivate a block type version
GET/_emdash/api/schema/collectionslistCollectionsList all collections
POST/_emdash/api/schema/collectionscreateCollectionCreate a collection
GET/_emdash/api/schema/collections/{slug}getCollectionGet a collection
PUT/_emdash/api/schema/collections/{slug}updateCollectionUpdate a collection
DELETE/_emdash/api/schema/collections/{slug}deleteCollectionDelete a collection
GET/_emdash/api/schema/collections/{slug}/fieldslistFieldsList fields for a collection
POST/_emdash/api/schema/collections/{slug}/fieldscreateFieldCreate a field
GET/_emdash/api/schema/collections/{slug}/fields/{fieldSlug}getFieldGet a field
PUT/_emdash/api/schema/collections/{slug}/fields/{fieldSlug}updateFieldUpdate a field
DELETE/_emdash/api/schema/collections/{slug}/fields/{fieldSlug}deleteFieldDelete a field
POST/_emdash/api/schema/collections/reorderreorderCollectionsReorder collections in the admin sidebar
POST/_emdash/api/schema/collections/{slug}/fields/reorderreorderFieldsReorder fields in a collection
GET/_emdash/api/schema/orphanslistOrphanedTablesList orphaned content tables
POST/_emdash/api/schema/orphans/{slug}registerOrphanedTableRegister an orphaned table as a collection

評論

方法路徑操作摘要
GET/_emdash/api/comments/{collection}/{contentId}listPublicCommentsList approved comments for content
POST/_emdash/api/comments/{collection}/{contentId}createCommentSubmit a new comment
GET/_emdash/api/admin/commentslistAdminCommentsList comments for moderation
GET/_emdash/api/admin/comments/countsgetCommentCountsGet comment status counts
POST/_emdash/api/admin/comments/bulkbulkCommentActionBulk approve, spam, trash, or delete comments
GET/_emdash/api/admin/comments/{id}getCommentGet a single comment
DELETE/_emdash/api/admin/comments/{id}deleteCommentPermanently delete a comment
PUT/_emdash/api/admin/comments/{id}/statusupdateCommentStatusChange comment status

分類法

方法路徑操作摘要
GET/_emdash/api/taxonomieslistTaxonomiesList all taxonomy definitions
GET/_emdash/api/taxonomies/{name}getTaxonomyGet a taxonomy definition
PUT/_emdash/api/taxonomies/{name}updateTaxonomyUpdate a taxonomy definition
DELETE/_emdash/api/taxonomies/{name}deleteTaxonomyDelete a taxonomy, its terms, and their content assignments
GET/_emdash/api/taxonomies/{name}/translationslistTaxonomyTranslationsList every locale variant of a taxonomy definition
POST/_emdash/api/taxonomies/{name}/reorderreorderTermsSet the manual order of one sibling group of terms
GET/_emdash/api/taxonomies/{name}/termslistTermsList terms for a taxonomy
POST/_emdash/api/taxonomies/{name}/termscreateTermCreate a term
GET/_emdash/api/taxonomies/{name}/terms/{slug}getTermGet a term by slug
PUT/_emdash/api/taxonomies/{name}/terms/{slug}updateTermUpdate a term
DELETE/_emdash/api/taxonomies/{name}/terms/{slug}deleteTermDelete a term

選單

方法路徑操作摘要
GET/_emdash/api/menuslistMenusList all menus with item counts
POST/_emdash/api/menuscreateMenuCreate a menu
GET/_emdash/api/menus/{name}getMenuGet a menu with all items
PUT/_emdash/api/menus/{name}updateMenuUpdate a menu
DELETE/_emdash/api/menus/{name}deleteMenuDelete a menu and its items
POST/_emdash/api/menus/{name}/itemscreateMenuItemAdd an item to a menu
PUT/_emdash/api/menus/{name}/items/{id}updateMenuItemUpdate a menu item
DELETE/_emdash/api/menus/{name}/items/{id}deleteMenuItemDelete a menu item
POST/_emdash/api/menus/{name}/reorderreorderMenuItemsBatch reorder menu items

區段

方法路徑操作摘要
GET/_emdash/api/sectionslistSectionsList sections
POST/_emdash/api/sectionscreateSectionCreate a section
GET/_emdash/api/sections/{slug}getSectionGet a section by slug
PUT/_emdash/api/sections/{slug}updateSectionUpdate a section
DELETE/_emdash/api/sections/{slug}deleteSectionDelete a section

小工具

方法路徑操作摘要
GET/_emdash/api/widget-areaslistWidgetAreasList all widget areas
POST/_emdash/api/widget-areascreateWidgetAreaCreate a widget area
GET/_emdash/api/widget-areas/{name}getWidgetAreaGet a widget area with widgets
DELETE/_emdash/api/widget-areas/{name}deleteWidgetAreaDelete a widget area and its widgets
POST/_emdash/api/widget-areas/{name}/widgetscreateWidgetAdd a widget to an area
PUT/_emdash/api/widget-areas/{name}/widgets/{id}updateWidgetUpdate a widget
DELETE/_emdash/api/widget-areas/{name}/widgets/{id}deleteWidgetDelete a widget
POST/_emdash/api/widget-areas/{name}/reorderreorderWidgetsReorder widgets in an area

設定

方法路徑操作摘要
GET/_emdash/api/settingsgetSettingsGet site settings
PUT/_emdash/api/settingsupdateSettingsUpdate site settings

搜尋

方法路徑操作摘要
GET/_emdash/api/searchsearchFull-text search across collections
GET/_emdash/api/search/suggestsearchSuggestAutocomplete search suggestions
POST/_emdash/api/search/rebuildrebuildSearchIndexRebuild the search index for a collection
POST/_emdash/api/search/enableenableSearchEnable or disable search for a collection
GET/_emdash/api/search/statsgetSearchStatsGet search index statistics

重新導向

方法路徑操作摘要
GET/_emdash/api/redirectslistRedirectsList redirects
POST/_emdash/api/redirectscreateRedirectCreate a redirect rule
GET/_emdash/api/redirects/{id}getRedirectGet a redirect
PUT/_emdash/api/redirects/{id}updateRedirectUpdate a redirect
DELETE/_emdash/api/redirects/{id}deleteRedirectDelete a redirect
GET/_emdash/api/redirects/404slistNotFoundEntriesList 404 log entries
POST/_emdash/api/redirects/404spruneNotFoundLogPrune old 404 log entries
DELETE/_emdash/api/redirects/404sclearNotFoundLogClear all 404 log entries
GET/_emdash/api/redirects/404s/summarygetNotFoundSummaryGet 404 summary grouped by path

使用者

方法路徑操作摘要
GET/_emdash/api/admin/userslistUsersList users
GET/_emdash/api/admin/users/{id}getUserGet user details
PUT/_emdash/api/admin/users/{id}updateUserUpdate a user
POST/_emdash/api/admin/users/{id}/disabledisableUserDisable a user account
POST/_emdash/api/admin/users/{id}/enableenableUserEnable a user account
GET/_emdash/api/admin/allowed-domainslistAllowedDomainsList allowed email domains
POST/_emdash/api/admin/allowed-domainscreateAllowedDomainAdd an allowed email domain
PUT/_emdash/api/admin/allowed-domains/{domain}updateAllowedDomainUpdate an allowed domain
DELETE/_emdash/api/admin/allowed-domains/{domain}deleteAllowedDomainRemove an allowed domain

站點遷移

方法路徑操作摘要
GET/_emdash/api/admin/transfer/capabilitiesgetTransferCapabilitiesGet site transfer capabilities
GET/_emdash/api/admin/transfer/importslistTransferImportsList site imports
POST/_emdash/api/admin/transfer/importscreateTransferImportCreate a site import
GET/_emdash/api/admin/transfer/imports/{id}getTransferImportGet a site import
GET/_emdash/api/admin/transfer/imports/{id}/missinglistTransferImportMissingFilesList package files still to upload
PUT/_emdash/api/admin/transfer/imports/{id}/files/{path}uploadTransferImportFileUpload one package file
POST/_emdash/api/admin/transfer/imports/{id}/analyzeanalyzeTransferImportAdvance import analysis
GET/_emdash/api/admin/transfer/imports/{id}/plangetTransferImportPlanGet an import plan
POST/_emdash/api/admin/transfer/imports/{id}/cancelcancelTransferImportCancel a site import
POST/_emdash/api/admin/transfer/imports/{id}/abandonabandonTransferImportAbandon a failed or cancelled import
POST/_emdash/api/admin/transfer/imports/{id}/executeexecuteTransferImportStart a planned import
POST/_emdash/api/admin/transfer/imports/{id}/advanceadvanceTransferImportAdvance an executing import
GET/_emdash/api/admin/transfer/imports/{id}/receiptgetTransferImportReceiptGet an import receipt
GET/_emdash/api/admin/transfer/exportslistTransferExportsList site exports
POST/_emdash/api/admin/transfer/exportscreateTransferExportStart a site export
GET/_emdash/api/admin/transfer/exports/{id}getTransferExportGet a site export
POST/_emdash/api/admin/transfer/exports/{id}/advanceadvanceTransferExportAdvance a site export
GET/_emdash/api/admin/transfer/exports/{id}/manifestgetTransferExportManifestDownload an export manifest
GET/_emdash/api/admin/transfer/exports/{id}/files/{path}downloadTransferExportFileDownload one export file
GET/_emdash/api/admin/transfer/exports/{id}/archivedownloadTransferExportArchiveDownload an export archive
GET/_emdash/api/admin/transfer/approvalslistTransferApprovalsList transfer approvals
POST/_emdash/api/admin/transfer/approvals/{id}/approveapproveTransferApprovalApprove a transfer request
POST/_emdash/api/admin/transfer/approvals/{id}/denydenyTransferApprovalDeny a transfer request

內容生命週期與署名

內容生命週期參考定義了 REST、MCP、CLI 與管理後台共用的狀態、修訂、權限、衝突與掛鉤行為。

內容讀取在可用時會傳回不透明的 _rev 權杖。對 PUT /content/{collection}/{id} 附上 _rev,即可避免覆蓋自讀取之後所發生的變更。過期的權杖會產生衝突;重試前請再次讀取該項目。CLI 對 content update 強制執行此檢查,而 REST 欄位對於刻意選擇無條件寫入的用戶端仍是選用的。

讀取並更新項目

修改前先讀取該項目:

GET /_emdash/api/content/articles/01JARTICLE0000000000000000
Authorization: Bearer $EMDASH_TOKEN

回應包含其欄位、發佈狀態和修訂權杖:

{
	"success": true,
	"data": {
		"item": {
			"id": "01JARTICLE0000000000000000",
			"type": "articles",
			"slug": "launch-notes",
			"status": "published",
			"data": { "title": "Launch notes" }
		},
		"_rev": "opaque-revision-token"
	}
}

只傳送需要變更的欄位,並附上該次讀取所得的權杖:

PUT /_emdash/api/content/articles/01JARTICLE0000000000000000
Authorization: Bearer $EMDASH_TOKEN
Content-Type: application/json

{
	"data": { "title": "Updated launch notes" },
	"_rev": "opaque-revision-token"
}

變更已發佈的項目會建立一份草稿,而先前的版本仍保持上線。請呼叫比較操作來檢視兩個版本,然後發佈或捨棄該草稿。取消發佈會保留內容及其發佈日期,取消任何待處理的排程,並將該項目從線上站點移除。

建立與更新的請求主體接受署名資訊,內容回應會包含主要署名與有序的署名清單。內容清單可以依儲存的署名 ID 篩選,也可以選擇納入作者的推斷署名。署名記錄本身的建立與管理是透過 MCP 署名工具提供,而不屬於公開的 REST 契約。

生命週期操作區分軟刪除與永久刪除。還原會把已移到垃圾桶的內容以草稿形式傳回,且不帶排程;永久刪除會移除垃圾桶中的項目,且無法復原。發佈、取消發佈、排程、取消排程、比較、捨棄草稿與複製是各自獨立的操作,因此用戶端可以一次只請求一項狀態轉換。

項目編輯鎖

集合可以在編輯者開啟項目時取得一個七分鐘的編輯鎖。請使用 /content/{collection}/{id}/lock 上的三個操作來讀取、取得或更新,以及釋放該租約。

讀取或取得的回應會顯示是否啟用了鎖定、呼叫者是否持有租約,以及目前由誰持有:

{
	"success": true,
	"data": {
		"enabled": true,
		"heldByCaller": false,
		"holder": {
			"userId": "01JUSER000000000000000000",
			"userName": "Ada",
			"acquiredAt": "2026-05-01T09:12:04.117Z",
			"expiresAt": "2026-05-01T09:19:04.117Z"
		}
	}
}

當集合停用了編輯鎖定時,enabled 為 false,也不會取得任何租約。再次取得同一個鎖,以及儲存項目,都會延長呼叫者所持有的租約。

取得操作的請求主體可以包含一個用來識別單一編輯工作階段的不透明 token,以及在使用者選擇取代另一位編輯者的租約時使用的 takeover: true。釋放鎖時,請把同一個權杖作為查詢參數傳入。這樣,同一帳號的第二個分頁就不會意外釋放第一個分頁的租約。

當另一位使用者持有租約時,受保護的內容寫入會傳回 409 ENTRY_LOCKED。錯誤詳情會指出持有者與到期時間。若要覆寫該鎖,對有請求主體的寫入請在 JSON 主體中傳送 "overrideLock": true,對沒有請求主體的 DELETE 操作則使用 ?overrideLock=true。

引用選擇

reference 欄位透過關聯把一個項目連結到另一個集合中的項目。它的值不屬於 data,並且以翻譯群組為鍵,因此一個項目的每個翻譯共用同一份選擇。

建立與更新的請求主體在 references 下攜帶選擇,以欄位 slug 為鍵,每個值是最多 1000 個項目 ID 的陣列,並依顯示順序排列。EmDash 會在寫入項目的同一個交易中寫入該選擇。以下更新會取代該項目的作者:

PUT /_emdash/api/content/articles/01JARTICLE0000000000000000
Authorization: Bearer $EMDASH_TOKEN
Content-Type: application/json

{
	"data": { "title": "Updated launch notes" },
	"references": { "author": ["01JAUTHOR00000000000000000"] },
	"_rev": "opaque-revision-token"
}

綁定到關聯子端的欄位,選擇的是指向正在寫入的那個項目的項目,這些項目本身沒有順序。關聯上限會在兩端都強制執行,因此,會讓某個被連結項目的父項目數超過關聯允許值的選擇,與自身連結了過多項目的選擇一樣會被拒絕。

在保留修訂的集合中,對已發佈項目所做的選擇變更會與該項目的其他待處理編輯一起暫存在草稿中。它會在項目發佈時生效,並隨草稿一起被捨棄。發佈時會依關聯上限重新檢查整份選擇。

讀取單一項目會傳回以欄位 slug 為鍵的 references。每個欄位包含其被連結項目的第一頁(共 50 個),若欄位包含更多項目則附帶 nextCursor,並報告每個項目的 ID、slug、集合、顯示標題、解析後的語系與翻譯群組。有權讀取草稿的呼叫者,會在草稿帶有暫存選擇時看到該暫存選擇。內容清單操作不包含引用。

關聯定義與連結遍歷屬於管理路由,不在 OpenAPI 中,也不屬於公開契約。請透過上述內容操作寫入與讀取選擇,並在站點上使用 getEmDashEntry() 和 getEmDashReferences() 來渲染它們。

翻譯

公開 REST 契約公開了內容翻譯與分類法定義翻譯。建立內容時接受 translationOf,建立分類法時使用同一個欄位來新增語系變體。內容詞彙操作會傳回感知語系的指派。

省略 locale 時,GET /taxonomies/{name} 會傳回站點預設語系的定義,只有在預設語系沒有定義時才退而使用語系代碼最小的定義。更新的行為則不同:省略 locale 時,它會變更語系代碼最小的那個定義。請傳入 locale,讓已翻譯分類法的更新作用於預期的定義。如果該語系沒有定義,更新會傳回 NOT_FOUND,而不會退而使用其他語系。翻譯操作會傳回共用群組中的每個定義,並提供 translationOf 接受的 ID。

label 和 labelSingular 屬於某個語系的定義。hierarchical 和 collections 屬於分類法本身:每個語系傳回相同的值,傳送其中任一個的更新會對每個語系生效。為已存在於其他語系的名稱建立定義時,無論請求是否傳送 translationOf,都會把它加入該分類法。新定義會沿用該分類法的 hierarchical 和 collections,而傳送了不同值的建立請求會傳回 VALIDATION_ERROR。

刪除分類法會移除其定義的所有語系、所有詞彙,以及這些詞彙對內容的所有指派。它不會刪除內容項目本身。

詞彙重新排序操作只改變一個同級群組。ids 陣列可以只包含該群組的一部分;列出的詞彙會互換各自現有的位置,未列出的詞彙保持原位。例如,以 ids: ["C", "A"] 對 [A, B, C] 重新排序,會得到 [C, B, A]。重新排序不會改變父子關係,並且一種詞彙順序會套用到其翻譯群組中的每個語系。

選單、分類法詞彙與署名的翻譯路由不在公開 REST 契約中。它們受支援的翻譯清單可透過 MCP 伺服器上的 menu_translations、taxonomy_term_translations 和 byline_translations 取得。

媒體端點

媒體操作涵蓋列出、上傳、中繼資料更新、圖片取代、資料夾、使用資訊,以及使用索引維護。媒體庫指南說明了面向使用者的工作流程,以及使用涵蓋範圍的含義。

列出與檢視媒體

GET /media 支援游標或編號頁面分頁、MIME 類型與檔名篩選、資料夾,以及選用的使用摘要。省略 folderId 會包含每個資料夾,傳入 folderId=unfiled 則只傳回主媒體庫。在清單或單一項目讀取上設定 includeUsage=1 可包含使用資訊;任何其他值都無效。

usage.count 統計的是不同的作用中內容資料列或語系,其目前已索引的來源引用了該媒體項目,再加上選用它的每項站點設定(logo、favicon、seo.defaultOgImage)。同一個項目中的重複引用只計一次,垃圾桶中的項目不計入。該數字僅對有權讀取草稿的呼叫者可見;其他獲授權的媒體讀取者會收到 count: null,因為該計數可能洩漏草稿內容。

GET /media/{id}/usage 會分頁傳回引用該媒體的內容項目。每一頁還會包含 siteSettings,即選用該媒體項目的站點設定,例如 [{ "setting": "favicon" }]。站點設定在每次請求時都從儲存的設定中讀取,不依賴使用索引。

每個使用結果都包含一個涵蓋狀態:

狀態含義
complete每個已註冊的集合都具有目前的使用涵蓋。
never沒有任何已註冊的集合完成過初始使用修復。
running修復正在進行中。
partial只有一部分已註冊的集合具有目前的涵蓋。
failed整個已註冊的集合在涵蓋上都失敗。
stale索引比其所描述的內容更舊。
unknown此 EmDash 版本無法識別所儲存的狀態。

只有 complete 才支援在已索引的欄位類型範圍內,將零計數視為完整。並行寫入期間,計數僅供參考;它們不會鎖定該媒體項目,也不保證刪除是安全的。使用索引涵蓋圖片與檔案欄位、repeater 圖片欄位、Portable Text 的圖片、圖庫與影片區塊,以及 EmDash 集合中保留的區塊版本所宣告的媒體。網站標誌、favicon 與預設社群圖片設定也會一併回報。使用資訊不包含自訂 Portable Text 區塊、應用程式程式碼、渲染後的 HTML、其他設定、選單、小工具、外掛資料、外部站點,或僅由提供者管理的資產。

直接 multipart 上傳

透過 EmDash 傳送檔案時,請將其作為 multipart 請求的 file 欄位提交:

curl --request POST \
	--header "Authorization: Bearer $EMDASH_TOKEN" \
	--form "file=@./cover.jpg;type=image/jpeg" \
	https://example.com/_emdash/api/media

curl 會自動加入 multipart 邊界。請勿手動設定 Content-Type 標頭。OpenAPI 的 MediaDirectUploadBody schema 列出了選用的中繼資料欄位,而回應 schema 則區分了新上傳與已去重的既有項目。

multipart 主體還可以包含新媒體項目的 alt 文字和 caption、圖片的 width 和 height、應套用其 MIME 類型允許清單的 fieldId,以及用來建立低品質預留位置的縮小版 thumbnail。省略或為空的 alt 或 caption 欄位會儲存為 null。新檔案會傳回 201 Created 並立即可用。位元組完全相同的檔案會以 200 OK 和 deduplicated: true 傳回既有的媒體項目;隨上傳一併傳送的 alt 和 caption 不會套用到它。

上傳目標流程

當用戶端可以直接上傳到相容 S3 的儲存時,請使用上傳目標流程。在確認成功之前,該媒體項目保持待處理狀態,也不會出現在標準媒體庫中。

  1. 請求上傳目標

    POST /_emdash/api/media/upload-url
    Authorization: Bearer $EMDASH_TOKEN
    Content-Type: application/json
    
    {
    	"filename": "cover.jpg",
    	"contentType": "image/jpeg",
    	"size": 102400
    }

    回應會提供 uploadUrl、method、headers、mediaId、storageKey 和到期時間。當 contentHash 與具有相同 MIME 類型和大小的既有檔案相符時,回應會改為設定 existing: true;請使用該媒體項目,不要再上傳或確認另一份副本。

  2. 上傳位元組

    使用傳回的方法和標頭。請依 EmDash 站點解析以根路徑開頭的相對 URL,並附上 Bearer 權杖。若是指向其他來源的絕對 URL,則只傳送傳回的上傳標頭。

  3. 確認上傳

    POST /_emdash/api/media/01JMEDIA000000000000000000/confirm
    Authorization: Bearer $EMDASH_TOKEN
    Content-Type: application/json
    
    {
    	"size": 102400,
    	"width": 1920,
    	"height": 1080
    }

    確認會驗證所儲存的物件,並把項目從 pending 變更為 ready。所提供的大小與尺寸必須與上傳的檔案相符。

本機儲存和原生 R2 會傳回同源的 EmDash 上傳目標。相容 S3 的儲存可能傳回簽署的外部 URL。在確認成功之前,待處理項目不會出現在標準媒體清單中。

上傳錯誤

下列錯誤需要不同的復原動作:

狀態代碼動作
400NO_FILE在 multipart 請求中加入 file 欄位,或傳送缺少的上傳主體。
400INVALID_TYPE使用與待處理媒體項目相符的允許 MIME 類型。
400VALIDATION_ERROR更正缺少或無效的中繼資料,包括超過所設定大小上限的值。
400FILE_NOT_FOUND在確認之前,先把物件上傳到傳回的目標。
400UPLOAD_SIZE_MISMATCH以正確的大小重新開始流程;宣告、上傳與確認的大小必須一致。
400 或 409INVALID_STATE重試之前先讀取該媒體項目。它可能已不再處於待處理狀態,或在確認期間已被另一個請求變更。
404NOT_FOUND使用既有的待處理媒體 ID。
413PAYLOAD_TOO_LARGE在開始另一次上傳之前,請縮小檔案大小,或提高 maxUploadSize。

媒體資料夾

資料夾名稱會去除前後空白,上限為 200 個字元,並在 Unicode 正規化與轉為小寫之後進行比較。因此,Photos、photos 和 PHOTOS 這類名稱會衝突。刪除資料夾會把其中的媒體傳回主媒體庫;它不會刪除媒體、變更媒體 ID 或 URL,也不會變更使用記錄。

修復媒體使用

媒體使用的啟用、進度、工作佇列、刪除清理與修復,都是位於 /_emdash/api/admin/media-usage/ 下的維運操作。工作階段使用者需要 schema:manage;Bearer 權杖還需要 admin scope。

如果追蹤處於關閉狀態,請在啟用之前暫停直接寫入資料庫的程式。設定進行期間,EmDash 會暫時封鎖透過其 API 傳送的內容與 schema 寫入,但無法阻止另一個直接寫入資料庫的處理程序。

  1. 停止直接寫入資料庫的程式,並等待它們進行中的寫入完成。
  2. 讀取啟用狀態。expanded 表示追蹤已關閉,activating 表示 EmDash 正在準備集合,active 表示新的媒體引用變更正在被追蹤。
  3. 傳送一次啟用請求,內容為 { "writersDrained": true }。
  4. 一次傳送一個進度請求,並等待每次傳回的 nextRequestInMs,直到啟用狀態變成 active。
  5. 恢復直接寫入資料庫。
  6. 持續傳送進度請求,直到歷史索引變成 ready 且 nextRequestInMs 為 null。

如果寫入請求逾時,或傳回 409 或 500,請在重試之前讀取啟用與進度狀態。已完成的批次仍會保留記錄。當 lastErrorCode 有值時,請讓直接寫入的程式保持停止,解決所回報的問題,然後傳送一次經過確認的重試。啟用一旦開始就無法取消或重設,因此請針對預備環境的副本測試此程序,並保留一份最新的資料庫備份。

工作清單操作會公開失敗或延遲的項目索引,但不會傳回內容、媒體引用、租約權杖、原始資料庫錯誤,也不會傳回確切的積壓數量。重試單一項目是冪等的。409 WORK_LEASE_ACTIVE 表示某個工作者仍在處理該項目;回應包含 details.leaseExpiresAt,因此請等到該時間之後,再讀取該項目並重試。409 WORK_CHANGED 表示另一個請求已變更該工作項目;請讀取它目前的狀態,而不要覆蓋較新的工作。

修復操作接受 { "scope": "collection", "collection": "articles" } 或 { "scope": "all" }。對所有集合的修復會以同步、依序的方式執行,因此在大型站點上可能耗時很長。200 回應仍可能回報 partial、failed 或 stale;在將修復視為完成之前,請檢查 data.status、各集合的狀態以及來源計數。

站點遷移

/_emdash/api/admin/transfer/ 下的遷移操作會把站點匯出為站點套件,並把套件匯入到空站點。站台轉移指南說明了匯出、上傳、分析、執行與回執的工作流程。

工作階段使用者需要 transfer:export 或 transfer:import 權限,這兩項權限只有管理員才擁有。Bearer 權杖需要 admin,或每個操作所指明的 transfer:export、transfer:analyze 或 transfer:execute scope。核准操作只接受已登入的工作階段。它們用來決定由 MCP 的 site_export_start 和 site_import_start 工具所發出的請求;REST 的匯出與執行操作不採用核准。

匯入執行期間,以及匯入失敗或被取消後、尚未被放棄之前,大多數其他寫入操作會傳回 503 TRANSFER_IMPORT_IN_PROGRESS。指南中列出了仍可使用的操作。

分頁

清單操作會在 OpenAPI 中描述其分頁參數。大多數採用游標分頁的操作接受不透明的 cursor 和 1 到 100 的 limit,預設為 50。請原封不動地傳回上一個回應的 nextCursor;不要檢視或自行建構它。部分專用清單使用不同的上限,因此產生的用戶端應遵循每個操作的 schema。

GET /media、GET /content/{collection} 和 GET /content/{collection}/trash 也接受從 1 起算的 page,以取代 cursor。編號頁面沒有 nextCursor,並會報告符合條件的項目數量:媒體為 totalCount,內容為 total。同時傳送 page 和 cursor 會傳回 400 驗證錯誤。

搜尋分詞器

啟用搜尋的操作會為每個集合儲存一種分詞器。在已啟用的集合上變更它,會重建該集合的索引。

值用途
porter unicode61預設值,適用於能從 Porter 詞幹提取中受益的英文內容。
unicode61使用詞間分隔符號、但不應使用英文詞幹提取的語言。
trigram沒有空格的文字(包括日文、中文、泰文、高棉文、寮文和緬甸文),或需要子字串比對的集合。短於三個 Unicode 字元的查詢不會傳回任何比對結果。

停用搜尋會保留所儲存的分詞器,供下次啟用時使用。重建操作使用該集合所儲存的分詞器與欄位權重。

評論與重新導向

公開的評論提交會進入審核佇列。管理端的評論操作可列出所有狀態、傳回計數、更新單一狀態、執行批次審核,以及永久刪除評論。429 回應表示已達提交頻率上限。

重新導向操作會分別管理重新導向規則與所記錄的 404 日誌。清除舊項目會移除請求主體所選取的項目,而 DELETE /redirects/404s 則會清空整份日誌。這兩個操作都不會刪除重新導向規則。