Skip to content

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.json

The document is generated from the same Zod schemas used by the API. It also reflects the configured maximum media upload size.

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.

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_TOKEN

Bearer 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.

For a state-changing request authenticated by a session cookie, include this header:

X-EmDash-Request: 1

Bearer-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.

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.

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.

MethodPathOperationSummary
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
MethodPathOperationSummary
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
MethodPathOperationSummary
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
MethodPathOperationSummary
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
MethodPathOperationSummary
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
MethodPathOperationSummary
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
MethodPathOperationSummary
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
MethodPathOperationSummary
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
MethodPathOperationSummary
GET/_emdash/api/settingsgetSettingsGet site settings
PUT/_emdash/api/settingsupdateSettingsUpdate site settings
MethodPathOperationSummary
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
MethodPathOperationSummary
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
MethodPathOperationSummary
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

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 the entry before changing it:

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

The 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/01JARTICLE0000000000000000
Authorization: Bearer $EMDASH_TOKEN
Content-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.

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.

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 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.

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:

StatusMeaning
completeEvery registered collection has current usage coverage.
neverNo registered collection has completed an initial usage repair.
runningA repair is in progress.
partialOnly part of the registered collection set has current coverage.
failedCoverage failed across the registered collection set.
staleThe index is older than the content it describes.
unknownThe 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.

Send a file through EmDash by posting it as the file field of a multipart request:

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

curl 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.

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.

  1. Request an upload target

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

    The response supplies uploadUrl, method, headers, mediaId, storageKey, and an expiry. When contentHash matches an existing file with the same MIME type and size, the response instead sets existing: true; use that media item and do not upload or confirm another copy.

  2. 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.

  3. Confirm the upload

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

    Confirmation verifies the stored object and changes the item from pending to ready. 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.

The following errors require a different recovery action:

StatusCodeAction
400NO_FILEAdd the file field to a multipart request, or send the missing upload body.
400INVALID_TYPEUse an allowed MIME type that matches the pending media item.
400VALIDATION_ERRORCorrect the missing or invalid metadata, including values above the configured size limit.
400FILE_NOT_FOUNDUpload the object to the returned target before confirming it.
400UPLOAD_SIZE_MISMATCHRestart the flow with the correct size; the declared, uploaded, and confirmed sizes must agree.
400 or 409INVALID_STATERead the media item before retrying. It may no longer be pending, or another request may have changed it during confirmation.
404NOT_FOUNDUse an existing pending media ID.
413PAYLOAD_TOO_LARGEReduce the file size or increase maxUploadSize before starting another upload.

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.

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.

  1. Stop direct database writers and wait for their in-progress writes to finish.
  2. Read the activation state. expanded means tracking is off, activating means EmDash is preparing collections, and active means new media-reference changes are tracked.
  3. Send one activation request with { "writersDrained": true }.
  4. Send progress requests one at a time, waiting for each returned nextRequestInMs, until activation is active.
  5. Resume direct database writes.
  6. Continue progress requests until historical indexing is ready and nextRequestInMs is null.

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.

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.

The search-enable operation stores a tokenizer for each collection. Changing it on an enabled collection rebuilds that collection’s index.

ValueUse
porter unicode61The default for English content that benefits from Porter stemming.
unicode61Languages that use word separators but should not use English stemming.
trigramText 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.

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.