REST API Reference
EmDash exposes a REST API at /_emdash/api/ for content management, media uploads, and schema operations.
Authentication
Section titled “Authentication”API requests require authentication via a Bearer token in the Authorization header:
Authorization: Bearer <token>Generate tokens through the admin interface or programmatically.
Response format
Section titled “Response format”All responses follow a consistent format. A successful response wraps the result in data:
{ "success": true, "data": { ... }}An error response includes a code, message, and optional details:
{ "success": false, "error": { "code": "ERROR_CODE", "message": "Human-readable message", "details": { ... } }}Content Endpoints
Section titled “Content Endpoints”List Content
Section titled “List Content”GET /_emdash/api/content/:collectionParameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
collection | string | Collection slug (path) |
cursor | string | Pagination cursor (query) |
limit | number | Items per page (query, default: 50) |
status | string | Filter by status (query) |
orderBy | string | Field to sort by (query) |
order | string | Sort direction: asc or desc (query) |
Response
Section titled “Response”{ "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 Content
Section titled “Get Content”GET /_emdash/api/content/:collection/:idResponse
Section titled “Response”{ "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" } }}Create Content
Section titled “Create Content”POST /_emdash/api/content/:collectionContent-Type: application/jsonRequest Body
Section titled “Request Body”{ "data": { "title": "New Post", "content": [...] }, "slug": "new-post", "status": "draft"}Response
Section titled “Response”{ "success": true, "data": { "item": { ... } }}Update Content
Section titled “Update Content”PUT /_emdash/api/content/:collection/:idContent-Type: application/jsonRequest Body
Section titled “Request Body”{ "data": { "title": "Updated Title" }, "status": "published"}Delete Content
Section titled “Delete Content”DELETE /_emdash/api/content/:collection/:idResponse
Section titled “Response”{ "success": true, "data": { "success": true }}Media Endpoints
Section titled “Media Endpoints”List Media
Section titled “List Media”GET /_emdash/api/media?includeUsage=1Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
cursor | string | Opaque pagination cursor |
limit | number | Items per page, from 1 to 100 (default: 50) |
mimeType | string | Filter by one or more comma-separated MIME types |
q | string | Case-insensitive filename search |
includeUsage | 1 | Include a coverage-aware usage summary on every returned item |
Response
Section titled “Response”{ "success": true, "data": { "items": [ { "id": "01HXK5MZSN...", "filename": "photo.jpg", "mimeType": "image/jpeg", "size": 102400, "width": 1920, "height": 1080, "url": "/_emdash/api/media/file/uploads/photo.jpg", "createdAt": "2025-01-24T12:00:00Z", "usage": { "count": 3, "coverage": { "scope": "all_content_collections", "status": "complete" } } } ], "nextCursor": "eyJpZCI6..." }}Get Media
Section titled “Get Media”GET /_emdash/api/media/:id?includeUsage=1includeUsage is optional on both list and get. Its only accepted value is 1. When omitted,
the usage property is omitted and the server does not run usage queries.
Usage Summaries
Section titled “Usage Summaries”usage.count is the number of distinct active EmDash content rows or locales whose selected
current indexed source references the media item. Repeated references and multiple source
variants for the same content entry count once. Trashed content does not count.
A numeric count can reveal draft-like content. It is returned only when a session user has
content:read_drafts, or when an API token has admin scope and its associated user also has
that permission. Other media readers receive usage.count: null; this is a successful redacted
response, not an error.
Every requested summary includes aggregate coverage for all currently registered content collections:
| Status | Meaning |
|---|---|
complete | Every registered collection has current, completed usage coverage |
never | No registered collection has completed an initial usage repair |
running | A usage repair is currently running |
partial | Coverage is mixed or only part of the registered scope was indexed |
failed | Coverage failed across the registered scope |
stale | Indexed coverage is outdated |
unknown | Stored coverage contains a state this version does not recognize |
Only complete supports a scoped complete-zero statement within the EmDash-managed fields
described below. Counts with any other status are indexed projections and may over-report or
under-report. Even complete results are advisory during concurrent writes; usage reads are not a
transactional lock and must not be used as a deletion guarantee.
Get Media Usage Details
Section titled “Get Media Usage Details”GET /_emdash/api/media/:id/usage?limit=50&cursor=...This endpoint requires media:read and content:read_drafts. Token-authenticated callers also
require admin scope; token scope does not bypass the associated user’s permissions.
limit controls content entry groups per page, from 1 to 100 (default: 50). Pagination never
splits the sources or occurrences for one returned entry group.
{ "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" } }}Authorized details include active and trashed entries. A non-null deletedAt identifies a
trashed entry. Sources are columns or draft_overlay; occurrences identify the supported field
and path without exposing internal index metadata.
Media usage covers local media references in top-level image and file fields, repeater image fields, and Portable Text image blocks managed by EmDash content collections. It does not scan custom code, rendered HTML, settings, menus, widgets, plugin-private data, external sites, or provider-only assets.
Create Media
Section titled “Create Media”POST /_emdash/api/mediaContent-Type: application/jsonRequest Body
Section titled “Request Body”{ "filename": "photo.jpg", "mimeType": "image/jpeg", "size": 102400, "width": 1920, "height": 1080, "storageKey": "uploads/photo.jpg"}Update Media
Section titled “Update Media”PUT /_emdash/api/media/:idContent-Type: application/jsonRequest Body
Section titled “Request Body”{ "alt": "Photo description", "caption": "Photo caption"}Delete Media
Section titled “Delete Media”DELETE /_emdash/api/media/:idList Media Usage Work
Section titled “List Media Usage Work”GET /_emdash/api/admin/media-usage/work?collection=posts&state=failed&limit=50&cursor=...Returns a bounded page of durable entry-indexing work for one current collection. The endpoint
requires schema:manage; bearer tokens also require the admin scope.
collection is required. state optionally filters pending, retry, leased, or failed
work. limit defaults to 50 and is capped at 100. cursor is opaque and comes from the previous
page’s nextCursor. The endpoint does not calculate an exact backlog count.
{ "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" }}Responses omit work versions, lease tokens, raw database errors, indexed content, media references, and exact counts.
Retry Media Usage Work
Section titled “Retry Media Usage Work”POST /_emdash/api/admin/media-usage/work/retryContent-Type: application/jsonX-EmDash-Request: 1Idempotently reopens or creates one durable entry job. It has the same authorization requirements as the list endpoint.
{ "collectionId": "01COLLECTION...", "contentId": "01CONTENT..."}A successful response returns changed and the current pending item. changed: false means the
job was already pending. A non-expired worker lease returns 409 WORK_LEASE_ACTIVE with
details.leaseExpiresAt; a concurrent mutation returns 409 WORK_CHANGED. Neither conflict
replaces newer work or exposes its lease token.
The list returns only known durable work. Retry can create work for the supplied identity in an active collection even when no work row exists, but it does not scan for historical gaps. Use collection-scoped Media Usage repair after imports or direct database writes. When scheduled maintenance is disabled, failed jobs remain visible and manually retryable, but no automatic freshness deadline is promised.
Recover Collection Deletion
Section titled “Recover Collection Deletion”GET /_emdash/api/admin/media-usage/collection-deletions?state=failed&limit=50&cursor=...Returns a bounded page of durable collection-deletion work. The list defaults to failed work;
limit defaults to 50 and is capped at 100. Items include the immutable collection ID, slug,
phase, attempts, eligibility/lease timestamps, stable error code, and update time. Lease tokens,
raw database errors, content, media references, and exact backlog counts are never returned.
POST /_emdash/api/admin/media-usage/collection-deletions/retryContent-Type: application/jsonX-EmDash-Request: 1
{ "collectionId": "01COLLECTION..." }Retry reopens failed, retrying, or expired-leased work without changing its phase. A live lease
returns 409 WORK_LEASE_ACTIVE; a concurrent state change returns 409 WORK_CHANGED. Both routes
require schema:manage, and bearer tokens also require the admin scope. They recover internal
index cleanup only and never delete media assets.
Repair Media Usage
Section titled “Repair Media Usage”POST /_emdash/api/admin/media-usage/repairContent-Type: application/jsonX-EmDash-Request: 1Repairs the content media usage index for one collection or for all content collections. This is an admin/operator endpoint: session-authenticated callers need schema:manage, and bearer tokens must have the admin scope because the route is under /_emdash/api/admin.
All-content repair runs synchronously and sequentially in the current version. It can be expensive on large sites, so callers should trigger it deliberately and wait for the response.
Request Body
Section titled “Request Body”Repair one collection:
{ "scope": "collection", "collection": "posts"}Repair all content collections:
{ "scope": "all"}The request body is required. Invalid slugs, unknown request keys, missing scope, and body-less requests return 400 instead of defaulting to all-content repair.
Response
Section titled “Response”The endpoint returns 200 when a repair invocation produces a structured result. Inspect data.status: failed and stale are repair-domain statuses, not transport errors.
{ "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" } ] }}Top-level response fields:
| Field | Type | Description |
|---|---|---|
status | complete | partial | failed | stale | Aggregate repair status |
indexedSourceCount | number | Sources indexed during repair |
failedSourceCount | number | Sources that failed during repair |
skippedSourceCount | number | Sources skipped, including stale conflicts |
deletedSourceCount | number | Stale usage rows deleted during repair |
collections | array | Per-collection repair summaries |
Collection summary fields:
| Field | Type | Description |
|---|---|---|
collection | string | Collection slug |
status | complete | partial | failed | stale | Collection repair status |
indexedSourceCount | number | Sources indexed for this collection |
failedSourceCount | number | Sources that failed for this collection |
skippedSourceCount | number | Sources skipped for this collection |
deletedSourceCount | number | Stale usage rows deleted for this collection |
lastErrorCode | string | null | Last collection repair error, when available |
startedAt | string | Repair start time |
completedAt | string | null | Completion time, or null for stale results |
Unknown collections return 200 with data.status: "failed" and a per-collection lastErrorCode such as COLLECTION_NOT_FOUND. Transport errors still use the standard error envelope, including 400, 401, 403, 413, and 500.
Get Media File
Section titled “Get Media File”GET /_emdash/api/media/file/:keyServes the actual file content. For local storage only.
Revision Endpoints
Section titled “Revision Endpoints”List Revisions
Section titled “List Revisions”GET /_emdash/api/content/:collection/:entryId/revisionsParameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
limit | number | Max revisions to return (default: 50) |
Response
Section titled “Response”{ "success": true, "data": { "items": [ { "id": "01HXK5MZSN...", "collection": "posts", "entryId": "01HXK5MZSN...", "data": { ... }, "createdAt": "2025-01-24T12:00:00Z" } ], "total": 5 }}Get Revision
Section titled “Get Revision”GET /_emdash/api/revisions/:revisionIdRestore Revision
Section titled “Restore Revision”POST /_emdash/api/revisions/:revisionId/restoreRestores content to this revision’s state and creates a new revision.
Schema Endpoints
Section titled “Schema Endpoints”List Collections
Section titled “List Collections”GET /_emdash/api/schema/collectionsResponse
Section titled “Response”{ "success": true, "data": { "items": [ { "id": "01HXK5MZSN...", "slug": "posts", "label": "Posts", "labelSingular": "Post", "supports": ["drafts", "revisions", "preview"] } ] }}Get Collection
Section titled “Get Collection”GET /_emdash/api/schema/collections/:slugParameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
includeFields | boolean | Include field definitions (query) |
Create Collection
Section titled “Create Collection”POST /_emdash/api/schema/collectionsContent-Type: application/jsonRequest Body
Section titled “Request Body”{ "slug": "products", "label": "Products", "labelSingular": "Product", "description": "Product catalog", "supports": ["drafts", "revisions"]}Update Collection
Section titled “Update Collection”PUT /_emdash/api/schema/collections/:slugContent-Type: application/jsonDelete Collection
Section titled “Delete Collection”DELETE /_emdash/api/schema/collections/:slugParameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
force | boolean | Delete even if collection has content (query) |
List Fields
Section titled “List Fields”GET /_emdash/api/schema/collections/:slug/fieldsCreate Field
Section titled “Create Field”POST /_emdash/api/schema/collections/:slug/fieldsContent-Type: application/jsonRequest Body
Section titled “Request Body”{ "slug": "price", "label": "Price", "type": "number", "required": true, "validation": { "min": 0 }}Update Field
Section titled “Update Field”PUT /_emdash/api/schema/collections/:collectionSlug/fields/:fieldSlugContent-Type: application/jsonDelete Field
Section titled “Delete Field”DELETE /_emdash/api/schema/collections/:collectionSlug/fields/:fieldSlugReorder Fields
Section titled “Reorder Fields”POST /_emdash/api/schema/collections/:slug/fields/reorderContent-Type: application/jsonRequest Body
Section titled “Request Body”{ "fieldSlugs": ["title", "content", "author", "publishedAt"]}Schema Export
Section titled “Schema Export”Export Schema (JSON)
Section titled “Export Schema (JSON)”GET /_emdash/api/schemaAccept: application/jsonExport Schema (TypeScript)
Section titled “Export Schema (TypeScript)”GET /_emdash/api/schema?format=typescriptAccept: text/typescriptReturns TypeScript interfaces for all collections.
Plugin Endpoints
Section titled “Plugin Endpoints”List Plugins
Section titled “List Plugins”GET /_emdash/api/admin/pluginsGet Plugin
Section titled “Get Plugin”GET /_emdash/api/admin/plugins/:idEnable Plugin
Section titled “Enable Plugin”POST /_emdash/api/admin/plugins/:id/enableDisable Plugin
Section titled “Disable Plugin”POST /_emdash/api/admin/plugins/:id/disableError Codes
Section titled “Error Codes”| Code | HTTP Status | Description |
|---|---|---|
NOT_FOUND | 404 | Resource not found |
VALIDATION_ERROR | 400 | Invalid input data |
UNAUTHORIZED | 401 | Missing or invalid token |
FORBIDDEN | 403 | Insufficient permissions |
CONTENT_LIST_ERROR | 500 | Failed to list content |
CONTENT_CREATE_ERROR | 500 | Failed to create content |
CONTENT_UPDATE_ERROR | 500 | Failed to update content |
CONTENT_DELETE_ERROR | 500 | Failed to delete content |
MEDIA_LIST_ERROR | 500 | Failed to list media |
MEDIA_CREATE_ERROR | 500 | Failed to create media |
SCHEMA_CREATE_ERROR | 500 | Schema operation failed |
SLUG_CONFLICT | 409 | Slug already exists |
RESERVED_SLUG | 400 | Slug is reserved |
Search Endpoints
Section titled “Search Endpoints”Global Search
Section titled “Global Search”GET /_emdash/api/search?q=hello+worldParameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
q | string | Search query (required) |
collections | string | Comma-separated collection slugs |
status | string | Filter by status (default: published) |
limit | number | Max results (default: 20) |
cursor | string | Pagination cursor |
Response
Section titled “Response”{ "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" }}Search Suggestions
Section titled “Search Suggestions”GET /_emdash/api/search/suggest?q=hel&limit=5Returns prefix-matched titles for autocomplete.
Configure Collection Search
Section titled “Configure Collection Search”POST /_emdash/api/search/enableContent-Type: application/json
{ "collection": "posts", "enabled": true, "tokenize": "trigram", "weights": { "title": 10, "content": 1 }}The optional tokenize field controls how SQLite FTS5 indexes the collection. Changing it on an
enabled collection rebuilds and repopulates that collection’s search index.
| Value | When to use |
|---|---|
porter unicode61 | Default. English-language content that benefits from Porter stemming, such as matching related word forms. Porter stemming is English-specific. |
unicode61 | Languages that use word separators but should not use English stemming. |
trigram | Languages with text that is not separated by spaces, including Japanese, Chinese, Thai, Khmer, Lao, and Burmese, or when substring matching is needed. Queries shorter than three Unicode characters return no matches. |
Omitting tokenize on a collection without a stored tokenizer uses porter unicode61. Disabling
search preserves the configured tokenizer for the next enable operation.
Rebuild Search Index
Section titled “Rebuild Search Index”POST /_emdash/api/search/rebuildContent-Type: application/json
{ "collection": "posts"}Rebuilds the FTS index for the specified collection using its stored tokenizer and field weights.
Search Stats
Section titled “Search Stats”GET /_emdash/api/search/statsReturns indexed document counts per collection.
Section Endpoints
Section titled “Section Endpoints”List Sections
Section titled “List Sections”GET /_emdash/api/sectionsGET /_emdash/api/sections?source=themeGET /_emdash/api/sections?search=newsletterGet Section
Section titled “Get Section”GET /_emdash/api/sections/:slugCreate Section
Section titled “Create Section”POST /_emdash/api/sectionsContent-Type: application/json
{ "slug": "my-section", "title": "My Section", "keywords": ["keyword1"], "content": [...]}Update Section
Section titled “Update Section”PUT /_emdash/api/sections/:slugDelete Section
Section titled “Delete Section”DELETE /_emdash/api/sections/:slugSettings Endpoints
Section titled “Settings Endpoints”Get All Settings
Section titled “Get All Settings”GET /_emdash/api/settingsUpdate Settings
Section titled “Update Settings”POST /_emdash/api/settingsContent-Type: application/json
{ "siteTitle": "My Site", "tagline": "A great site", "postsPerPage": 10}Menu Endpoints
Section titled “Menu Endpoints”List Menus
Section titled “List Menus”GET /_emdash/api/menusGet Menu
Section titled “Get Menu”GET /_emdash/api/menus/:nameCreate Menu
Section titled “Create Menu”POST /_emdash/api/menusContent-Type: application/json
{ "name": "footer", "label": "Footer Navigation"}Update Menu
Section titled “Update Menu”PUT /_emdash/api/menus/:nameDelete Menu
Section titled “Delete Menu”DELETE /_emdash/api/menus/:nameAdd Menu Item
Section titled “Add Menu Item”POST /_emdash/api/menus/:name/itemsContent-Type: application/json
{ "type": "page", "referenceCollection": "pages", "referenceId": "page_about", "label": "About Us"}Reorder Menu Items
Section titled “Reorder Menu Items”POST /_emdash/api/menus/:name/reorderContent-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 } ]}Taxonomy Endpoints
Section titled “Taxonomy Endpoints”List Taxonomy Definitions
Section titled “List Taxonomy Definitions”GET /_emdash/api/taxonomiesCreate Taxonomy
Section titled “Create Taxonomy”POST /_emdash/api/taxonomiesContent-Type: application/json
{ "name": "genre", "label": "Genres", "labelSingular": "Genre", "hierarchical": true, "collections": ["books", "movies"]}List Terms
Section titled “List Terms”GET /_emdash/api/taxonomies/:name/termsTerms come back in their manual order (see Reorder Terms). A new term is added to the end of its sibling group.
Create Term
Section titled “Create Term”POST /_emdash/api/taxonomies/:name/termsContent-Type: application/json
{ "slug": "tutorials", "label": "Tutorials", "parentId": "term_abc", "description": "How-to guides"}Update Term
Section titled “Update Term”PUT /_emdash/api/taxonomies/:name/terms/:slugDelete Term
Section titled “Delete Term”DELETE /_emdash/api/taxonomies/:name/terms/:slugReorder Terms
Section titled “Reorder Terms”POST /_emdash/api/taxonomies/:name/reorderContent-Type: application/json
{ "parentId": "term_abc", "ids": ["term_news", "term_featured"]}Sets the order of one sibling group. parentId names the parent whose children
are being ordered; omit it (or send null) for the top level, which for a flat
taxonomy is every term. Reordering never changes a term’s parent — use
Update Term for that.
ids may be a subset of the group: the terms you list are permuted within the
positions they already occupy, and every other member keeps its place. That
matters when a locale doesn’t render the whole group, and it means a stale list
can’t bury the terms it left out. An id outside the group is rejected with
REORDER_MISMATCH, and at most 100 ids may be sent at once.
Because the terms you leave out hold their absolute positions, a one-step move
in a partial list can carry a term past siblings that list didn’t include. If
[A, B, C] is the full group and you send ["C", "A"] — because B isn’t
translated into the locale you’re working in — the result is [C, B, A]: A
and C swapped as asked, and a listing that does show B sees A move two
places rather than one.
There is no locale parameter. A term holds one position across every locale it
is translated into, so an id may be either a term id or a translation group, and
ordering a taxonomy in one locale orders it in all of them. Sites that need
different orders per locale should use separate taxonomies.
Set Entry Terms
Section titled “Set Entry Terms”POST /_emdash/api/content/:collection/:id/terms/:taxonomyContent-Type: application/json
{ "termIds": ["term_news", "term_featured"]}Widget Area Endpoints
Section titled “Widget Area Endpoints”List Widget Areas
Section titled “List Widget Areas”GET /_emdash/api/widget-areasGet Widget Area
Section titled “Get Widget Area”GET /_emdash/api/widget-areas/:nameCreate Widget Area
Section titled “Create Widget Area”POST /_emdash/api/widget-areasContent-Type: application/json
{ "name": "sidebar", "label": "Main Sidebar", "description": "Appears on posts"}Delete Widget Area
Section titled “Delete Widget Area”DELETE /_emdash/api/widget-areas/:nameAdd Widget
Section titled “Add Widget”POST /_emdash/api/widget-areas/:name/widgetsContent-Type: application/json
{ "type": "content", "title": "About", "content": [...]}Update Widget
Section titled “Update Widget”PUT /_emdash/api/widget-areas/:name/widgets/:idDelete Widget
Section titled “Delete Widget”DELETE /_emdash/api/widget-areas/:name/widgets/:idReorder Widgets
Section titled “Reorder Widgets”POST /_emdash/api/widget-areas/:name/reorderContent-Type: application/json
{ "widgetIds": ["widget_1", "widget_2", "widget_3"]}User Management Endpoints
Section titled “User Management Endpoints”List Users
Section titled “List Users”GET /_emdash/api/admin/usersGET /_emdash/api/admin/users?role=40GET /_emdash/api/admin/users?search=johnGet User
Section titled “Get User”GET /_emdash/api/admin/users/:idUpdate User
Section titled “Update User”PUT /_emdash/api/admin/users/:idContent-Type: application/json
{ "name": "John Doe", "role": 40}Enable User
Section titled “Enable User”POST /_emdash/api/admin/users/:id/enableDisable User
Section titled “Disable User”POST /_emdash/api/admin/users/:id/disableAuthentication Endpoints
Section titled “Authentication Endpoints”Setup Status
Section titled “Setup Status”GET /_emdash/api/setup/statusReturns whether setup is complete and if users exist.
Passkey Login
Section titled “Passkey Login”POST /_emdash/api/auth/passkey/optionsGet WebAuthn authentication options.
POST /_emdash/api/auth/passkey/verifyContent-Type: application/json
{ "id": "credential-id", "rawId": "...", "response": {...}, "type": "public-key"}Verify passkey and create session.
Magic Link
Section titled “Magic Link”POST /_emdash/api/auth/magic-link/sendContent-Type: application/json
{ "email": "user@example.com"}GET /_emdash/api/auth/magic-link/verify?token=xxxLogout
Section titled “Logout”POST /_emdash/api/auth/logoutCurrent User
Section titled “Current User”GET /_emdash/api/auth/meInvite User
Section titled “Invite User”POST /_emdash/api/auth/inviteContent-Type: application/json
{ "email": "newuser@example.com", "role": 30}Passkey Management
Section titled “Passkey Management”GET /_emdash/api/auth/passkeyList user’s passkeys.
POST /_emdash/api/auth/passkey/register/optionsPOST /_emdash/api/auth/passkey/register/verifyRegister new passkey.
PATCH /_emdash/api/auth/passkey/:idContent-Type: application/json
{ "name": "MacBook Pro"}Rename passkey.
DELETE /_emdash/api/auth/passkey/:idDelete passkey.
Import Endpoints
Section titled “Import Endpoints”Analyze WordPress Export
Section titled “Analyze WordPress Export”POST /_emdash/api/import/wordpress/analyzeContent-Type: multipart/form-data
file: <WXR file>Execute WordPress Import
Section titled “Execute WordPress Import”POST /_emdash/api/import/wordpress/executeContent-Type: application/json
{ "analysisId": "...", "options": { "includeMedia": true, "includeTaxonomies": true, "includeMenus": true }}Rate Limiting
Section titled “Rate Limiting”API endpoints may be rate-limited based on deployment configuration. When rate-limited, responses include:
HTTP/1.1 429 Too Many RequestsRetry-After: 60The API supports CORS for browser requests. Configure allowed origins in your deployment.