EmDash expone una API REST en /_emdash/api/ para gestión de contenido, subida de multimedia y operaciones de esquema.
Autenticación
Las solicitudes de API requieren autenticación mediante un token Bearer en el encabezado Authorization:
Authorization: Bearer <token>
Genere tokens a través de la interfaz de administración o programáticamente.
Formato de respuesta
Todas las respuestas siguen un formato consistente. Una respuesta exitosa envuelve el resultado en data:
{
"success": true,
"data": { ... }
}
Una respuesta de error incluye un código, mensaje y detalles opcionales:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message",
"details": { ... }
}
}
Endpoints de contenido
Listar contenido
GET /_emdash/api/content/:collection
Parámetros
| Parámetro | Tipo | Descripción |
|---|---|---|
collection | string | Slug de la colección (ruta) |
cursor | string | Cursor de paginación (query) |
limit | number | Elementos por página (query, predeterminado: 50) |
status | string | Filtrar por estado (query) |
orderBy | string | Campo para ordenar (query) |
order | string | Dirección de orden: asc o desc (query) |
Respuesta
{
"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..."
}
}
Obtener contenido
GET /_emdash/api/content/:collection/:id
Respuesta
{
"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"
}
}
}
Crear contenido
POST /_emdash/api/content/:collection
Content-Type: application/json
Cuerpo de la solicitud
{
"data": {
"title": "New Post",
"content": [...]
},
"slug": "new-post",
"status": "draft"
}
Respuesta
{
"success": true,
"data": {
"item": { ... }
}
}
Actualizar contenido
PUT /_emdash/api/content/:collection/:id
Content-Type: application/json
Cuerpo de la solicitud
{
"data": {
"title": "Updated Title"
},
"status": "published"
}
Eliminar contenido
DELETE /_emdash/api/content/:collection/:id
Respuesta
{
"success": true,
"data": {
"success": true
}
}
Endpoints de multimedia
Listar multimedia
GET /_emdash/api/media?includeUsage=1
Parámetros
| Parámetro | Tipo | Descripción |
|---|---|---|
cursor | string | Cursor de paginación opaco |
limit | number | Elementos por página, de 1 a 100 (predeterminado: 50) |
mimeType | string | Filtrar por uno o más tipos MIME separados por comas |
q | string | Búsqueda de nombre de archivo insensible a mayúsculas |
includeUsage | 1 | Incluir resumen de usage con cobertura en cada elemento devuelto |
Respuesta
{
"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..."
}
}
Obtener multimedia
GET /_emdash/api/media/:id?includeUsage=1
includeUsage es opcional tanto en listar como en obtener. Su único valor aceptado es 1. Cuando se omite,
la propiedad usage se omite y el servidor no ejecuta consultas de uso.
Resúmenes de uso
usage.count es el número de filas de contenido o locales activos distintos de EmDash cuya fuente
indexada actual seleccionada referencia el elemento multimedia. Las referencias repetidas y múltiples variantes
de fuente para la misma entrada de contenido cuentan una vez. El contenido en la papelera no cuenta.
Un conteo numérico puede revelar contenido similar a borradores. Se devuelve solo cuando un usuario de sesión tiene
content:read_drafts, o cuando un token de API tiene el scope admin y su usuario asociado también tiene
ese permiso. Otros lectores de multimedia reciben usage.count: null; esto es una respuesta redactada exitosa,
no un error.
Cada resumen solicitado incluye cobertura agregada para todas las colecciones de contenido actualmente registradas:
| Estado | Significado |
|---|---|
complete | Cada colección registrada tiene cobertura de uso actual y completada |
never | Ninguna colección registrada ha completado una reparación de uso inicial |
running | Una reparación de uso está actualmente en ejecución |
partial | La cobertura es mixta o solo parte del alcance registrado fue indexado |
failed | La cobertura falló en todo el alcance registrado |
stale | La cobertura indexada está desactualizada |
unknown | La cobertura almacenada contiene un estado que esta versión no reconoce |
Solo complete soporta una declaración de cero completo con alcance dentro de los campos gestionados por EmDash
descritos a continuación. Los conteos con cualquier otro estado son proyecciones indexadas y pueden sobre-reportar o
sub-reportar. Incluso los resultados completos son consultivos durante escrituras concurrentes; las lecturas de uso no son un
bloqueo transaccional y no deben usarse como garantía de eliminación.
Obtener detalles de uso de multimedia
GET /_emdash/api/media/:id/usage?limit=50&cursor=...
Este endpoint requiere media:read y content:read_drafts. Los llamadores autenticados por token también
requieren el scope admin; el scope del token no omite los permisos del usuario asociado.
limit controla los grupos de entradas de contenido por página, de 1 a 100 (predeterminado: 50). La paginación nunca
divide las fuentes o ocurrencias para un grupo de entrada devuelto.
{
"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"
}
}
}
Los detalles autorizados incluyen entradas activas y eliminadas. Un deletedAt no nulo identifica una
entrada eliminada. Las fuentes son columns o draft_overlay; las ocurrencias identifican el campo soportado
y la ruta sin exponer metadatos internos del índice.
El uso de multimedia cubre referencias de multimedia local en campos de imagen y archivo de nivel superior, campos de imagen de repetidor y bloques de imagen de Portable Text gestionados por las colecciones de contenido de EmDash. No escanea código personalizado, HTML renderizado, configuraciones, menús, widgets, datos privados de plugins, sitios externos o activos exclusivos del proveedor.
Crear multimedia
POST /_emdash/api/media
Content-Type: application/json
Cuerpo de la solicitud
{
"filename": "photo.jpg",
"mimeType": "image/jpeg",
"size": 102400,
"width": 1920,
"height": 1080,
"storageKey": "uploads/photo.jpg"
}
Actualizar multimedia
PUT /_emdash/api/media/:id
Content-Type: application/json
Cuerpo de la solicitud
{
"alt": "Photo description",
"caption": "Photo caption"
}
Eliminar multimedia
DELETE /_emdash/api/media/:id
Reparar uso de multimedia
POST /_emdash/api/admin/media-usage/repair
Content-Type: application/json
X-EmDash-Request: 1
Repara el índice de uso de multimedia de contenido para una colección o para todas las colecciones de contenido. Este es un endpoint de administrador/operador: los llamadores autenticados por sesión necesitan schema:manage, y los tokens Bearer deben tener el scope admin porque la ruta está bajo /_emdash/api/admin.
La reparación de todo el contenido se ejecuta sincrónicamente y secuencialmente en la versión actual. Puede ser costosa en sitios grandes, por lo que los llamadores deben activarla deliberadamente y esperar la respuesta.
Cuerpo de la solicitud
Reparar una colección:
{
"scope": "collection",
"collection": "posts"
}
Reparar todas las colecciones de contenido:
{
"scope": "all"
}
El cuerpo de la solicitud es obligatorio. Slugs inválidos, claves de solicitud desconocidas, scope faltante y solicitudes sin cuerpo devuelven 400 en lugar de reparar todo el contenido por defecto.
Respuesta
El endpoint devuelve 200 cuando una invocación de reparación produce un resultado estructurado. Inspeccione data.status: failed y stale son estados del dominio de reparación, no errores de transporte.
{
"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"
}
]
}
}
Campos de respuesta de nivel superior:
| Campo | Tipo | Descripción |
|---|---|---|
status | complete | partial | failed | stale | Estado de reparación agregado |
indexedSourceCount | number | Fuentes indexadas durante la reparación |
failedSourceCount | number | Fuentes que fallaron durante la reparación |
skippedSourceCount | number | Fuentes omitidas, incluyendo conflictos obsoletos |
deletedSourceCount | number | Filas de uso obsoletas eliminadas durante la reparación |
collections | array | Resúmenes de reparación por colección |
Campos de resumen por colección:
| Campo | Tipo | Descripción |
|---|---|---|
collection | string | Slug de la colección |
status | complete | partial | failed | stale | Estado de reparación de la colección |
indexedSourceCount | number | Fuentes indexadas para esta colección |
failedSourceCount | number | Fuentes que fallaron para esta colección |
skippedSourceCount | number | Fuentes omitidas para esta colección |
deletedSourceCount | number | Filas de uso obsoletas eliminadas para esta colección |
lastErrorCode | string | null | Último error de reparación de colección, cuando disponible |
startedAt | string | Hora de inicio de la reparación |
completedAt | string | null | Hora de finalización, o null para resultados obsoletos |
Las colecciones desconocidas devuelven 200 con data.status: "failed" y un lastErrorCode por colección como COLLECTION_NOT_FOUND. Los errores de transporte siguen usando el sobre de error estándar, incluyendo 400, 401, 403, 413 y 500.
Obtener archivo multimedia
GET /_emdash/api/media/file/:key
Sirve el contenido real del archivo. Solo para almacenamiento local.
Endpoints de revisiones
Listar revisiones
GET /_emdash/api/content/:collection/:entryId/revisions
Parámetros
| Parámetro | Tipo | Descripción |
|---|---|---|
limit | number | Máx. revisiones a devolver (predeterminado: 50) |
Respuesta
{
"success": true,
"data": {
"items": [
{
"id": "01HXK5MZSN...",
"collection": "posts",
"entryId": "01HXK5MZSN...",
"data": { ... },
"createdAt": "2025-01-24T12:00:00Z"
}
],
"total": 5
}
}
Obtener revisión
GET /_emdash/api/revisions/:revisionId
Restaurar revisión
POST /_emdash/api/revisions/:revisionId/restore
Restaura el contenido al estado de esta revisión y crea una nueva revisión.
Endpoints de esquema
Listar colecciones
GET /_emdash/api/schema/collections
Respuesta
{
"success": true,
"data": {
"items": [
{
"id": "01HXK5MZSN...",
"slug": "posts",
"label": "Posts",
"labelSingular": "Post",
"supports": ["drafts", "revisions", "preview"]
}
]
}
}
Obtener colección
GET /_emdash/api/schema/collections/:slug
Parámetros
| Parámetro | Tipo | Descripción |
|---|---|---|
includeFields | boolean | Incluir definiciones de campos (query) |
Crear colección
POST /_emdash/api/schema/collections
Content-Type: application/json
Cuerpo de la solicitud
{
"slug": "products",
"label": "Products",
"labelSingular": "Product",
"description": "Product catalog",
"supports": ["drafts", "revisions"]
}
Actualizar colección
PUT /_emdash/api/schema/collections/:slug
Content-Type: application/json
Eliminar colección
DELETE /_emdash/api/schema/collections/:slug
Parámetros
| Parámetro | Tipo | Descripción |
|---|---|---|
force | boolean | Eliminar incluso si la colección tiene contenido (query) |
Listar campos
GET /_emdash/api/schema/collections/:slug/fields
Crear campo
POST /_emdash/api/schema/collections/:slug/fields
Content-Type: application/json
Cuerpo de la solicitud
{
"slug": "price",
"label": "Price",
"type": "number",
"required": true,
"validation": {
"min": 0
}
}
Actualizar campo
PUT /_emdash/api/schema/collections/:collectionSlug/fields/:fieldSlug
Content-Type: application/json
Eliminar campo
DELETE /_emdash/api/schema/collections/:collectionSlug/fields/:fieldSlug
Reordenar campos
POST /_emdash/api/schema/collections/:slug/fields/reorder
Content-Type: application/json
Cuerpo de la solicitud
{
"fieldSlugs": ["title", "content", "author", "publishedAt"]
}
Exportación de esquema
Exportar esquema (JSON)
GET /_emdash/api/schema
Accept: application/json
Exportar esquema (TypeScript)
GET /_emdash/api/schema?format=typescript
Accept: text/typescript
Devuelve interfaces TypeScript para todas las colecciones.
Endpoints de plugins
Listar plugins
GET /_emdash/api/admin/plugins
Obtener plugin
GET /_emdash/api/admin/plugins/:id
Activar plugin
POST /_emdash/api/admin/plugins/:id/enable
Desactivar plugin
POST /_emdash/api/admin/plugins/:id/disable
Códigos de error
| Código | Estado HTTP | Descripción |
|---|---|---|
NOT_FOUND | 404 | Recurso no encontrado |
VALIDATION_ERROR | 400 | Datos de entrada inválidos |
UNAUTHORIZED | 401 | Token faltante o inválido |
FORBIDDEN | 403 | Permisos insuficientes |
CONTENT_LIST_ERROR | 500 | Error al listar contenido |
CONTENT_CREATE_ERROR | 500 | Error al crear contenido |
CONTENT_UPDATE_ERROR | 500 | Error al actualizar contenido |
CONTENT_DELETE_ERROR | 500 | Error al eliminar contenido |
MEDIA_LIST_ERROR | 500 | Error al listar multimedia |
MEDIA_CREATE_ERROR | 500 | Error al crear multimedia |
SCHEMA_CREATE_ERROR | 500 | Operación de esquema fallida |
SLUG_CONFLICT | 409 | El slug ya existe |
RESERVED_SLUG | 400 | El slug está reservado |
Endpoints de búsqueda
Búsqueda global
GET /_emdash/api/search?q=hello+world
Parámetros
| Parámetro | Tipo | Descripción |
|---|---|---|
q | string | Consulta de búsqueda (requerido) |
collections | string | Slugs de colecciones separados por comas |
status | string | Filtrar por estado (predeterminado: published) |
limit | number | Máx. resultados (predeterminado: 20) |
cursor | string | Cursor de paginación |
Respuesta
{
"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"
}
}
Sugerencias de búsqueda
GET /_emdash/api/search/suggest?q=hel&limit=5
Devuelve títulos con coincidencia de prefijo para autocompletado.
Reconstruir índice de búsqueda
POST /_emdash/api/search/rebuild
Reconstruir el índice FTS para todas o colecciones específicas.
Estadísticas de búsqueda
GET /_emdash/api/search/stats
Devuelve conteos de documentos indexados por colección.
Endpoints de secciones
Listar secciones
GET /_emdash/api/sections
GET /_emdash/api/sections?source=theme
GET /_emdash/api/sections?search=newsletter
Obtener sección
GET /_emdash/api/sections/:slug
Crear sección
POST /_emdash/api/sections
Content-Type: application/json
{
"slug": "my-section",
"title": "My Section",
"keywords": ["keyword1"],
"content": [...]
}
Actualizar sección
PUT /_emdash/api/sections/:slug
Eliminar sección
DELETE /_emdash/api/sections/:slug
Endpoints de configuración
Obtener toda la configuración
GET /_emdash/api/settings
Actualizar configuración
POST /_emdash/api/settings
Content-Type: application/json
{
"siteTitle": "My Site",
"tagline": "A great site",
"postsPerPage": 10
}
Endpoints de menús
Listar menús
GET /_emdash/api/menus
Obtener menú
GET /_emdash/api/menus/:name
Crear menú
POST /_emdash/api/menus
Content-Type: application/json
{
"name": "main",
"label": "Main Navigation",
"items": []
}
Actualizar menú
PUT /_emdash/api/menus/:name
Eliminar menú
DELETE /_emdash/api/menus/:name
Agregar elemento de menú
POST /_emdash/api/menus/:name/items
Content-Type: application/json
{
"label": "About",
"url": "/about",
"position": 0
}
Reordenar elementos de menú
POST /_emdash/api/menus/:name/reorder
Content-Type: application/json
{
"itemIds": ["item_1", "item_2", "item_3"]
}
Endpoints de taxonomías
Listar definiciones de taxonomías
GET /_emdash/api/taxonomies
Crear taxonomía
POST /_emdash/api/taxonomies
Content-Type: application/json
{
"name": "categories",
"label": "Categories",
"hierarchical": true,
"collections": ["posts"]
}
Listar términos
GET /_emdash/api/taxonomies/:name/terms
Crear término
POST /_emdash/api/taxonomies/:name/terms
Content-Type: application/json
{
"slug": "tutorials",
"label": "Tutorials",
"parentId": "term_abc",
"description": "How-to guides"
}
Actualizar término
PUT /_emdash/api/taxonomies/:name/terms/:slug
Eliminar término
DELETE /_emdash/api/taxonomies/:name/terms/:slug
Establecer términos de entrada
POST /_emdash/api/content/:collection/:id/terms/:taxonomy
Content-Type: application/json
{
"termIds": ["term_news", "term_featured"]
}
Endpoints de áreas de widgets
Listar áreas de widgets
GET /_emdash/api/widget-areas
Obtener área de widgets
GET /_emdash/api/widget-areas/:name
Crear área de widgets
POST /_emdash/api/widget-areas
Content-Type: application/json
{
"name": "sidebar",
"label": "Main Sidebar",
"description": "Appears on posts"
}
Eliminar área de widgets
DELETE /_emdash/api/widget-areas/:name
Agregar widget
POST /_emdash/api/widget-areas/:name/widgets
Content-Type: application/json
{
"type": "content",
"title": "About",
"content": [...]
}
Actualizar widget
PUT /_emdash/api/widget-areas/:name/widgets/:id
Eliminar widget
DELETE /_emdash/api/widget-areas/:name/widgets/:id
Reordenar widgets
POST /_emdash/api/widget-areas/:name/reorder
Content-Type: application/json
{
"widgetIds": ["widget_1", "widget_2", "widget_3"]
}
Endpoints de gestión de usuarios
Listar usuarios
GET /_emdash/api/admin/users
GET /_emdash/api/admin/users?role=40
GET /_emdash/api/admin/users?search=john
Obtener usuario
GET /_emdash/api/admin/users/:id
Actualizar usuario
PUT /_emdash/api/admin/users/:id
Content-Type: application/json
{
"name": "John Doe",
"role": 40
}
Activar usuario
POST /_emdash/api/admin/users/:id/enable
Desactivar usuario
POST /_emdash/api/admin/users/:id/disable
Endpoints de autenticación
Estado de configuración
GET /_emdash/api/setup/status
Devuelve si la configuración está completa y si existen usuarios.
Inicio de sesión con Passkey
POST /_emdash/api/auth/passkey/options
Obtener opciones de autenticación WebAuthn.
POST /_emdash/api/auth/passkey/verify
Content-Type: application/json
{
"id": "credential-id",
"rawId": "...",
"response": {...},
"type": "public-key"
}
Verificar passkey y crear sesión.
Magic Link
POST /_emdash/api/auth/magic-link/send
Content-Type: application/json
{
"email": "[email protected]"
}
GET /_emdash/api/auth/magic-link/verify?token=xxx
Cerrar sesión
POST /_emdash/api/auth/logout
Usuario actual
GET /_emdash/api/auth/me
Invitar usuario
POST /_emdash/api/auth/invite
Content-Type: application/json
{
"email": "[email protected]",
"role": 30
}
Gestión de Passkeys
GET /_emdash/api/auth/passkey
Listar passkeys del usuario.
POST /_emdash/api/auth/passkey/register/options
POST /_emdash/api/auth/passkey/register/verify
Registrar nuevo passkey.
PATCH /_emdash/api/auth/passkey/:id
Content-Type: application/json
{
"name": "MacBook Pro"
}
Renombrar passkey.
DELETE /_emdash/api/auth/passkey/:id
Eliminar passkey.
Endpoints de importación
Analizar exportación de WordPress
POST /_emdash/api/import/wordpress/analyze
Content-Type: multipart/form-data
file: <WXR file>
Ejecutar importación de WordPress
POST /_emdash/api/import/wordpress/execute
Content-Type: application/json
{
"analysisId": "...",
"options": {
"includeMedia": true,
"includeTaxonomies": true,
"includeMenus": true
}
}
Limitación de tasa
Los endpoints de API pueden estar limitados en tasa según la configuración del despliegue. Cuando se limita la tasa, las respuestas incluyen:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
CORS
La API soporta CORS para solicitudes del navegador. Configure los orígenes permitidos en su despliegue.