REST API Reference
EmDash exposes its supported application programming interface (API) under /_emdash/api/. Use the generated OpenAPI 3.1 document for request parameters, bodies, response schemas, status codes, and client generation:
GET /_emdash/api/openapi.jsonThe document is generated from the same Zod schemas used by the API. It also reflects the configured maximum media upload size.
Public contract boundary
Section titled “Public contract boundary”The OpenAPI document is the boundary of the supported REST API. EmDash also has routes for its admin interface and protocol workflows. A route that exists in the source tree but is absent from OpenAPI is not a supported REST operation for external clients.
This distinction applies to backup, byline administration, relation traversal, plugin management, setup, import, and authentication routes. Use the backup guide for backups and the MCP byline tools for supported byline management. OAuth endpoints are protocol endpoints; discover them from the metadata described in the MCP OAuth section instead of treating them as application REST endpoints.
Authentication and authorization
Section titled “Authentication and authorization”Most operations accept either an EmDash session cookie or a Bearer token. Send a personal access token or OAuth access token in the Authorization header:
Authorization: Bearer $EMDASH_TOKENBearer tokens are limited by their scopes and the associated user’s role. Session requests use the user’s role. See user roles and token scopes for the authorization model.
GET and POST /_emdash/api/comments/{collection}/{contentId} are public. The GET operation returns approved comments; the POST operation submits a comment for moderation. The remaining comment-moderation operations require authentication.
Cross-site request forgery protection
Section titled “Cross-site request forgery protection”For a state-changing request authenticated by a session cookie, include this header:
X-EmDash-Request: 1Bearer-token requests do not require the header because they do not use ambient browser credentials. Browser requests to a public write operation must either send the header or have an Origin that matches the EmDash site’s public or request origin.
Response envelopes
Section titled “Response envelopes”A successful JSON response sets success to true and places the operation-specific result in data:
{ "success": true, "data": { "items": [] }}An error sets success to false and includes a stable machine-readable code and a message. Some errors also include structured details:
{ "success": false, "error": { "code": "NOT_FOUND", "message": "Content item not found" }}Use the status codes and error schemas on each OpenAPI operation. Common statuses are 400 for invalid input, 401 for missing or invalid credentials, 403 for insufficient scope or permission, 404 for a missing resource, 409 for a state conflict, 413 for an oversized upload, 422 for a save rejected by a plugin, and 500 for an internal failure.
Endpoint inventory
Section titled “Endpoint inventory”The operation ID is stable within the generated contract and is commonly used as the method name by OpenAPI client generators. The following inventory is checked against the generated OpenAPI document.
Content
Section titled “Content”| Method | Path | Operation | Summary |
|---|---|---|---|
GET | /_emdash/api/content/{collection} | listContent | List content items |
POST | /_emdash/api/content/{collection} | createContent | Create a content item |
GET | /_emdash/api/content/{collection}/{id} | getContent | Get a content item |
PUT | /_emdash/api/content/{collection}/{id} | updateContent | Update a content item |
DELETE | /_emdash/api/content/{collection}/{id} | deleteContent | Delete a content item (soft delete) |
POST | /_emdash/api/content/{collection}/{id}/publish | publishContent | Publish a content item |
POST | /_emdash/api/content/{collection}/{id}/unpublish | unpublishContent | Unpublish a content item |
POST | /_emdash/api/content/{collection}/{id}/schedule | scheduleContent | Schedule content for future publishing |
DELETE | /_emdash/api/content/{collection}/{id}/schedule | unscheduleContent | Cancel scheduled publishing |
POST | /_emdash/api/content/{collection}/{id}/duplicate | duplicateContent | Duplicate a content item |
POST | /_emdash/api/content/{collection}/{id}/restore | restoreContent | Restore a content item from trash |
DELETE | /_emdash/api/content/{collection}/{id}/permanent | permanentDeleteContent | Permanently delete a content item |
GET | /_emdash/api/content/{collection}/{id}/compare | compareContent | Compare live and draft revisions |
POST | /_emdash/api/content/{collection}/{id}/discard-draft | discardDraft | Discard draft changes |
GET | /_emdash/api/content/{collection}/{id}/lock | getEntryLock | Read the entry’s edit lock |
POST | /_emdash/api/content/{collection}/{id}/lock | acquireEntryLock | Take or refresh the entry’s edit lock |
DELETE | /_emdash/api/content/{collection}/{id}/lock | releaseEntryLock | Release the caller’s edit lock |
GET | /_emdash/api/content/{collection}/{id}/translations | getContentTranslations | Get translations for a content item |
GET | /_emdash/api/content/{collection}/{id}/terms/{taxonomy} | getContentTerms | Get taxonomy terms assigned to a content item |
POST | /_emdash/api/content/{collection}/{id}/terms/{taxonomy} | setContentTerms | Set taxonomy terms on a content item |
GET | /_emdash/api/content/{collection}/authors | listContentAuthors | List distinct authors of a collection’s content |
GET | /_emdash/api/content/{collection}/trash | listTrashedContent | List trashed content items |
| Method | Path | Operation | Summary |
|---|---|---|---|
GET | /_emdash/api/media | listMedia | List media items |
POST | /_emdash/api/media | uploadMedia | Upload a media item |
GET | /_emdash/api/media/folders | listMediaFolders | List media folders |
POST | /_emdash/api/media/folders | createMediaFolder | Create a media folder |
GET | /_emdash/api/media/folders/{id} | getMediaFolder | Get a media folder |
PUT | /_emdash/api/media/folders/{id} | updateMediaFolder | Update a media folder |
DELETE | /_emdash/api/media/folders/{id} | deleteMediaFolder | Delete a media folder |
GET | /_emdash/api/media/{id} | getMedia | Get a media item |
PUT | /_emdash/api/media/{id} | updateMedia | Update media metadata |
DELETE | /_emdash/api/media/{id} | deleteMedia | Delete a media item |
GET | /_emdash/api/media/{id}/usage | getMediaUsage | Get media usage details |
PUT | /_emdash/api/media/{id}/replace | replaceMediaImage | Replace a media image |
POST | /_emdash/api/admin/media-usage/repair | repairMediaUsage | Repair media usage indexes |
GET | /_emdash/api/admin/media-usage/progress | getMediaUsageProgress | Get media usage indexing progress |
POST | /_emdash/api/admin/media-usage/progress | advanceMediaUsageProgress | Advance media usage indexing |
GET | /_emdash/api/admin/media-usage/work | listMediaUsageWork | List durable media usage work |
GET | /_emdash/api/admin/media-usage/activation | getMediaUsageActivation | Get media usage activation status |
POST | /_emdash/api/admin/media-usage/activation | advanceMediaUsageActivation | Advance media usage activation |
POST | /_emdash/api/admin/media-usage/work/retry | retryMediaUsageWork | Retry one durable media usage job |
GET | /_emdash/api/admin/media-usage/collection-deletions | listMediaUsageCollectionDeletions | List durable collection deletions |
POST | /_emdash/api/admin/media-usage/collection-deletions/retry | retryMediaUsageCollectionDeletion | Retry one collection deletion |
POST | /_emdash/api/media/upload-url | getMediaUploadUrl | Get a media upload target |
POST | /_emdash/api/media/{id}/confirm | confirmMediaUpload | Confirm a media upload |
PUT | /_emdash/api/media/{id}/upload | uploadPendingMedia | Upload a pending media file through EmDash |
Schema
Section titled “Schema”| Method | Path | Operation | Summary |
|---|---|---|---|
GET | /_emdash/api/schema/collections | listCollections | List all collections |
POST | /_emdash/api/schema/collections | createCollection | Create a collection |
GET | /_emdash/api/schema/collections/{slug} | getCollection | Get a collection |
PUT | /_emdash/api/schema/collections/{slug} | updateCollection | Update a collection |
DELETE | /_emdash/api/schema/collections/{slug} | deleteCollection | Delete a collection |
GET | /_emdash/api/schema/collections/{slug}/fields | listFields | List fields for a collection |
POST | /_emdash/api/schema/collections/{slug}/fields | createField | Create a field |
GET | /_emdash/api/schema/collections/{slug}/fields/{fieldSlug} | getField | Get a field |
PUT | /_emdash/api/schema/collections/{slug}/fields/{fieldSlug} | updateField | Update a field |
DELETE | /_emdash/api/schema/collections/{slug}/fields/{fieldSlug} | deleteField | Delete a field |
POST | /_emdash/api/schema/collections/reorder | reorderCollections | Reorder collections in the admin sidebar |
POST | /_emdash/api/schema/collections/{slug}/fields/reorder | reorderFields | Reorder fields in a collection |
GET | /_emdash/api/schema/orphans | listOrphanedTables | List orphaned content tables |
POST | /_emdash/api/schema/orphans/{slug} | registerOrphanedTable | Register an orphaned table as a collection |
Comments
Section titled “Comments”| Method | Path | Operation | Summary |
|---|---|---|---|
GET | /_emdash/api/comments/{collection}/{contentId} | listPublicComments | List approved comments for content |
POST | /_emdash/api/comments/{collection}/{contentId} | createComment | Submit a new comment |
GET | /_emdash/api/admin/comments | listAdminComments | List comments for moderation |
GET | /_emdash/api/admin/comments/counts | getCommentCounts | Get comment status counts |
POST | /_emdash/api/admin/comments/bulk | bulkCommentAction | Bulk approve, spam, trash, or delete comments |
GET | /_emdash/api/admin/comments/{id} | getComment | Get a single comment |
DELETE | /_emdash/api/admin/comments/{id} | deleteComment | Permanently delete a comment |
PUT | /_emdash/api/admin/comments/{id}/status | updateCommentStatus | Change comment status |
Taxonomies
Section titled “Taxonomies”| Method | Path | Operation | Summary |
|---|---|---|---|
GET | /_emdash/api/taxonomies | listTaxonomies | List all taxonomy definitions |
GET | /_emdash/api/taxonomies/{name} | getTaxonomy | Get a taxonomy definition |
PUT | /_emdash/api/taxonomies/{name} | updateTaxonomy | Update a taxonomy definition |
DELETE | /_emdash/api/taxonomies/{name} | deleteTaxonomy | Delete a taxonomy, its terms, and their content assignments |
GET | /_emdash/api/taxonomies/{name}/translations | listTaxonomyTranslations | List every locale variant of a taxonomy definition |
POST | /_emdash/api/taxonomies/{name}/reorder | reorderTerms | Set the manual order of one sibling group of terms |
GET | /_emdash/api/taxonomies/{name}/terms | listTerms | List terms for a taxonomy |
POST | /_emdash/api/taxonomies/{name}/terms | createTerm | Create a term |
GET | /_emdash/api/taxonomies/{name}/terms/{slug} | getTerm | Get a term by slug |
PUT | /_emdash/api/taxonomies/{name}/terms/{slug} | updateTerm | Update a term |
DELETE | /_emdash/api/taxonomies/{name}/terms/{slug} | deleteTerm | Delete a term |
| Method | Path | Operation | Summary |
|---|---|---|---|
GET | /_emdash/api/menus | listMenus | List all menus with item counts |
POST | /_emdash/api/menus | createMenu | Create a menu |
GET | /_emdash/api/menus/{name} | getMenu | Get a menu with all items |
PUT | /_emdash/api/menus/{name} | updateMenu | Update a menu |
DELETE | /_emdash/api/menus/{name} | deleteMenu | Delete a menu and its items |
POST | /_emdash/api/menus/{name}/items | createMenuItem | Add an item to a menu |
PUT | /_emdash/api/menus/{name}/items/{id} | updateMenuItem | Update a menu item |
DELETE | /_emdash/api/menus/{name}/items/{id} | deleteMenuItem | Delete a menu item |
POST | /_emdash/api/menus/{name}/reorder | reorderMenuItems | Batch reorder menu items |
Sections
Section titled “Sections”| Method | Path | Operation | Summary |
|---|---|---|---|
GET | /_emdash/api/sections | listSections | List sections |
POST | /_emdash/api/sections | createSection | Create a section |
GET | /_emdash/api/sections/{slug} | getSection | Get a section by slug |
PUT | /_emdash/api/sections/{slug} | updateSection | Update a section |
DELETE | /_emdash/api/sections/{slug} | deleteSection | Delete a section |
Widgets
Section titled “Widgets”| Method | Path | Operation | Summary |
|---|---|---|---|
GET | /_emdash/api/widget-areas | listWidgetAreas | List all widget areas |
POST | /_emdash/api/widget-areas | createWidgetArea | Create a widget area |
GET | /_emdash/api/widget-areas/{name} | getWidgetArea | Get a widget area with widgets |
DELETE | /_emdash/api/widget-areas/{name} | deleteWidgetArea | Delete a widget area and its widgets |
POST | /_emdash/api/widget-areas/{name}/widgets | createWidget | Add a widget to an area |
PUT | /_emdash/api/widget-areas/{name}/widgets/{id} | updateWidget | Update a widget |
DELETE | /_emdash/api/widget-areas/{name}/widgets/{id} | deleteWidget | Delete a widget |
POST | /_emdash/api/widget-areas/{name}/reorder | reorderWidgets | Reorder widgets in an area |
Settings
Section titled “Settings”| Method | Path | Operation | Summary |
|---|---|---|---|
GET | /_emdash/api/settings | getSettings | Get site settings |
PUT | /_emdash/api/settings | updateSettings | Update site settings |
Search
Section titled “Search”| Method | Path | Operation | Summary |
|---|---|---|---|
GET | /_emdash/api/search | search | Full-text search across collections |
GET | /_emdash/api/search/suggest | searchSuggest | Autocomplete search suggestions |
POST | /_emdash/api/search/rebuild | rebuildSearchIndex | Rebuild the search index for a collection |
POST | /_emdash/api/search/enable | enableSearch | Enable or disable search for a collection |
GET | /_emdash/api/search/stats | getSearchStats | Get search index statistics |
Redirects
Section titled “Redirects”| Method | Path | Operation | Summary |
|---|---|---|---|
GET | /_emdash/api/redirects | listRedirects | List redirects |
POST | /_emdash/api/redirects | createRedirect | Create a redirect rule |
GET | /_emdash/api/redirects/{id} | getRedirect | Get a redirect |
PUT | /_emdash/api/redirects/{id} | updateRedirect | Update a redirect |
DELETE | /_emdash/api/redirects/{id} | deleteRedirect | Delete a redirect |
GET | /_emdash/api/redirects/404s | listNotFoundEntries | List 404 log entries |
POST | /_emdash/api/redirects/404s | pruneNotFoundLog | Prune old 404 log entries |
DELETE | /_emdash/api/redirects/404s | clearNotFoundLog | Clear all 404 log entries |
GET | /_emdash/api/redirects/404s/summary | getNotFoundSummary | Get 404 summary grouped by path |
| Method | Path | Operation | Summary |
|---|---|---|---|
GET | /_emdash/api/admin/users | listUsers | List users |
GET | /_emdash/api/admin/users/{id} | getUser | Get user details |
PUT | /_emdash/api/admin/users/{id} | updateUser | Update a user |
POST | /_emdash/api/admin/users/{id}/disable | disableUser | Disable a user account |
POST | /_emdash/api/admin/users/{id}/enable | enableUser | Enable a user account |
GET | /_emdash/api/admin/allowed-domains | listAllowedDomains | List allowed email domains |
POST | /_emdash/api/admin/allowed-domains | createAllowedDomain | Add an allowed email domain |
PUT | /_emdash/api/admin/allowed-domains/{domain} | updateAllowedDomain | Update an allowed domain |
DELETE | /_emdash/api/admin/allowed-domains/{domain} | deleteAllowedDomain | Remove an allowed domain |
Content lifecycle and bylines
Section titled “Content lifecycle and bylines”Content reads return an opaque _rev token when one is available. Send _rev with PUT /content/{collection}/{id} to prevent overwriting a change made since the read. A stale token produces a conflict; read the item again before retrying. The CLI makes this check mandatory for content update, while the REST field remains optional for clients that deliberately choose an unconditional write.
Read and update an entry
Section titled “Read and update an entry”Read the entry before changing it:
GET /_emdash/api/content/articles/01JARTICLE0000000000000000Authorization: Bearer $EMDASH_TOKENThe response contains its fields, publication state, and revision token:
{ "success": true, "data": { "item": { "id": "01JARTICLE0000000000000000", "type": "articles", "slug": "launch-notes", "status": "published", "data": { "title": "Launch notes" } }, "_rev": "opaque-revision-token" }}Send only the fields that need to change, together with the token from that read:
PUT /_emdash/api/content/articles/01JARTICLE0000000000000000Authorization: Bearer $EMDASH_TOKENContent-Type: application/json
{ "data": { "title": "Updated launch notes" }, "_rev": "opaque-revision-token"}Changing a published entry creates a draft while the previous version stays live. Call the compare operation to review both versions, then publish the draft or discard it. Unpublishing keeps the content and its publication date but removes it from the live site.
Creation and update bodies accept byline credits, and content responses include the primary byline and ordered credits. The content list can filter by stored byline IDs and optionally include an author’s inferred byline. Creating and managing the byline records themselves is available through the MCP byline tools, not the public REST contract.
The lifecycle operations distinguish a soft delete from permanent deletion. Restore acts on trashed content; permanent deletion removes a trashed item and cannot be undone. Publish, unpublish, schedule, unschedule, compare, discard-draft, and duplicate are separate operations so clients can request one state transition at a time.
Entry edit lock
Section titled “Entry edit lock”Collections can take a seven-minute edit lock when an editor opens an entry. Use the three operations on /content/{collection}/{id}/lock to read, acquire or refresh, and release the lease.
A read or acquire response shows whether locking is enabled, whether the caller holds the lease, and who currently holds it:
{ "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" } }}When a collection has edit locking disabled, enabled is false and no lease is taken. Acquiring the same lock again and saving the entry both extend a lease held by the caller.
The acquire body can include an opaque token that identifies one editing session and takeover: true when the user chooses to replace another editor’s lease. Pass the same token as a query parameter when releasing the lock. A second tab from the same account then cannot release the first tab’s lease accidentally.
When another user holds the lease, protected content writes return 409 ENTRY_LOCKED. The error details identify the holder and expiry. To override the lock, send "overrideLock": true in the JSON body for a write that has a body, or ?overrideLock=true for a DELETE operation that has no body.
Translations and relations
Section titled “Translations and relations”The public REST contract exposes content translations and taxonomy-definition translations. Content creation accepts translationOf, and taxonomy creation uses the same field to add a locale variant. The content-term operations return locale-aware assignments.
GET /taxonomies/{name} returns the site’s default-locale definition when locale is omitted, falling back to the lowest locale code only when the default locale has no definition. An update behaves differently: when locale is omitted, it changes the definition with the lowest locale code. Pass locale so a translated-taxonomy update reaches the intended definition. If that locale has no definition, the update returns NOT_FOUND instead of falling back to another locale. The translations operation returns every definition in the shared group and supplies the IDs accepted by translationOf.
Deleting a taxonomy removes every locale of its definition, all its terms, and all assignments of those terms to content. It does not delete the content entries themselves.
The term-reorder operation changes one sibling group. The ids array may contain only part of that group; listed terms exchange their existing positions and omitted terms stay where they are. For example, reordering [A, B, C] with ids: ["C", "A"] produces [C, B, A]. Reordering does not change parent relationships, and one term order applies across every locale in its translation group.
Menu, taxonomy-term, and byline translation routes are not in the public REST contract. Their supported translation listings are available through menu_translations, taxonomy_term_translations, and byline_translations on the MCP server.
EmDash has internal routes that support relation editing in the admin, but they are absent from OpenAPI. External REST clients should use the relation and reference fields described by the collection schema instead of calling those internal routes.
Media endpoints
Section titled “Media endpoints”Media operations cover listing, upload, metadata updates, image replacement, folders, usage information, and usage-index maintenance. The media library guide explains the user-facing workflow and the meaning of usage coverage.
List and inspect media
Section titled “List and inspect media”GET /media supports cursor or numbered-page pagination, MIME-type and filename filters, folders, and optional usage summaries. Omit folderId to include every folder, or pass folderId=unfiled to return only the Main library. Set includeUsage=1 on a list or single-item read to include usage information; any other value is invalid.
usage.count counts distinct active content rows or locales whose current indexed source references the media item. Repeated references in one entry count once, and trashed entries do not count. The number is visible only to callers who may read drafts; other authorized media readers receive count: null because the count could reveal draft content.
Every usage result includes a coverage status:
| Status | Meaning |
|---|---|
complete | Every registered collection has current usage coverage. |
never | No registered collection has completed an initial usage repair. |
running | A repair is in progress. |
partial | Only part of the registered collection set has current coverage. |
failed | Coverage failed across the registered collection set. |
stale | The index is older than the content it describes. |
unknown | The stored state is not recognized by this EmDash version. |
Only complete supports treating a zero count as complete within the indexed field types. Counts are advisory during concurrent writes; they do not lock the media item or guarantee that deletion is safe. Usage indexing covers image and file fields, repeater image fields, and Portable Text image blocks in EmDash collections. It does not scan application code, rendered HTML, settings, menus, widgets, plugin data, external sites, or provider-only assets.
Direct multipart upload
Section titled “Direct multipart upload”Send a file through EmDash by posting it as the file field of a multipart request:
curl --request POST \ --header "Authorization: Bearer $EMDASH_TOKEN" \ --form "file=@./cover.jpg;type=image/jpeg" \ https://example.com/_emdash/api/mediacurl adds the multipart boundary. Do not set the Content-Type header manually. The OpenAPI MediaDirectUploadBody schema lists the optional metadata fields and the response schemas distinguish a new upload from a deduplicated existing item.
The multipart body can also include image width, height, a fieldId whose MIME-type allowlist should be applied, and a downscaled thumbnail used to create a low-quality placeholder. A new file returns 201 Created and is ready immediately. Identical bytes return the existing media item with 200 OK and deduplicated: true.
Upload target flow
Section titled “Upload target flow”Use the upload-target flow when the client can upload directly to S3-compatible storage. The media item remains pending and does not appear in the standard library until the confirmation succeeds.
-
Request an upload target
POST /_emdash/api/media/upload-urlAuthorization: Bearer $EMDASH_TOKENContent-Type: application/json{"filename": "cover.jpg","contentType": "image/jpeg","size": 102400}The response supplies
uploadUrl,method,headers,mediaId,storageKey, and an expiry. WhencontentHashmatches an existing file with the same MIME type and size, the response instead setsexisting: true; use that media item and do not upload or confirm another copy. -
Upload the bytes
Use the returned method and headers. Resolve a root-relative URL against the EmDash site and include the Bearer token. Send only the returned upload headers to an absolute URL on another origin.
-
Confirm the upload
POST /_emdash/api/media/01JMEDIA000000000000000000/confirmAuthorization: Bearer $EMDASH_TOKENContent-Type: application/json{"size": 102400,"width": 1920,"height": 1080}Confirmation verifies the stored object and changes the item from
pendingtoready. Supplied size and dimensions must match the uploaded file.
Local storage and native R2 return a same-origin EmDash upload target. S3-compatible storage may return a signed external URL. Pending items stay out of the standard media list until confirmation succeeds.
Upload errors
Section titled “Upload errors”The following errors require a different recovery action:
| Status | Code | Action |
|---|---|---|
400 | NO_FILE | Add the file field to a multipart request, or send the missing upload body. |
400 | INVALID_TYPE | Use an allowed MIME type that matches the pending media item. |
400 | VALIDATION_ERROR | Correct the missing or invalid metadata, including values above the configured size limit. |
400 | FILE_NOT_FOUND | Upload the object to the returned target before confirming it. |
400 | UPLOAD_SIZE_MISMATCH | Restart the flow with the correct size; the declared, uploaded, and confirmed sizes must agree. |
400 or 409 | INVALID_STATE | Read the media item before retrying. It may no longer be pending, or another request may have changed it during confirmation. |
404 | NOT_FOUND | Use an existing pending media ID. |
413 | PAYLOAD_TOO_LARGE | Reduce the file size or increase maxUploadSize before starting another upload. |
Media folders
Section titled “Media folders”Folder names are trimmed, limited to 200 characters, and compared after Unicode normalization and lowercasing. Names such as Photos, photos, and PHOTOS therefore conflict. Deleting a folder returns its media to the Main library; it does not delete media, change media IDs or URLs, or change usage records.
Repair media usage
Section titled “Repair media usage”Media usage activation, progress, work queues, deletion cleanup, and repair are operator operations under /_emdash/api/admin/media-usage/. Session users need schema:manage; Bearer tokens also need the admin scope.
If tracking is off, pause direct database writers before activation. EmDash temporarily blocks content and schema writes sent through its APIs while setup runs, but it cannot stop another process that writes directly to the database.
- Stop direct database writers and wait for their in-progress writes to finish.
- Read the activation state.
expandedmeans tracking is off,activatingmeans EmDash is preparing collections, andactivemeans new media-reference changes are tracked. - Send one activation request with
{ "writersDrained": true }. - Send progress requests one at a time, waiting for each returned
nextRequestInMs, until activation isactive. - Resume direct database writes.
- Continue progress requests until historical indexing is
readyandnextRequestInMsisnull.
If a write request times out or returns 409 or 500, read the activation and progress state before retrying. Completed batches remain recorded. When lastErrorCode is set, keep direct writers stopped, resolve the reported problem, and send one confirmed retry. Activation cannot be cancelled or reset after it starts, so test this procedure against a staging copy and keep a current database backup.
The work-list operations expose failed or delayed entry indexing without returning content, media references, lease tokens, raw database errors, or an exact backlog count. Retrying one item is idempotent. 409 WORK_LEASE_ACTIVE means a worker is still processing the item; the response includes details.leaseExpiresAt, so wait until that time and read the item again before retrying. 409 WORK_CHANGED means another request changed the work item; read its current state rather than overwriting the newer work.
The repair operation accepts either { "scope": "collection", "collection": "articles" } or { "scope": "all" }. An all-collection repair runs synchronously and sequentially, so it can take a long time on a large site. A 200 response can still report partial, failed, or stale; inspect data.status, the per-collection status, and the source counts before treating the repair as complete.
Pagination
Section titled “Pagination”List operations describe their pagination parameters in OpenAPI. Most cursor-paginated operations accept an opaque cursor and a limit from 1 to 100, defaulting to 50. Return the previous response’s nextCursor unchanged; do not inspect or construct it. Some media operations also support numbered pages, and some specialized lists use different limits, so generated clients should follow each operation’s schema.
Search tokenizers
Section titled “Search tokenizers”The search-enable operation stores a tokenizer for each collection. Changing it on an enabled collection rebuilds that collection’s index.
| Value | Use |
|---|---|
porter unicode61 | The default for English content that benefits from Porter stemming. |
unicode61 | Languages that use word separators but should not use English stemming. |
trigram | Text without spaces, including Japanese, Chinese, Thai, Khmer, Lao, and Burmese, or collections that need substring matching. Queries shorter than three Unicode characters return no matches. |
Disabling search preserves the stored tokenizer for the next enable operation. The rebuild operation uses the collection’s stored tokenizer and field weights.
Comments and redirects
Section titled “Comments and redirects”Public comment submissions enter the moderation queue. Admin comment operations list all statuses, return counts, update one status, perform bulk moderation, and permanently delete a comment. A 429 response means the submission rate limit was reached.
Redirect operations manage redirect rules and the recorded 404 log separately. Pruning removes entries selected by the request body, while DELETE /redirects/404s clears the entire log. Neither operation deletes redirect rules.