---
title: REST API Reference
description: HTTP endpoints for content, media, and schema management.
---

import { Aside } from "@astrojs/starlight/components";

EmDash exposes a REST API at `/_emdash/api/` for content management, media uploads, and schema operations.

## Authentication

API requests require authentication via Bearer token:

```http
Authorization: Bearer <token>
```

Generate tokens through the admin interface or programmatically.

## Response Format

All responses follow a consistent format:

```json
// Success
{
  "success": true,
  "data": { ... }
}

// Error
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable message",
    "details": { ... }
  }
}
```

## Content Endpoints

### List Content

```http
GET /_emdash/api/content/:collection
```

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

```json
{
  "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

```http
GET /_emdash/api/content/:collection/:id
```

#### Response

```json
{
  "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

```http
POST /_emdash/api/content/:collection
Content-Type: application/json
```

#### Request Body

```json
{
  "data": {
    "title": "New Post",
    "content": [...]
  },
  "slug": "new-post",
  "status": "draft"
}
```

#### Response

```json
{
  "success": true,
  "data": {
    "item": { ... }
  }
}
```

### Update Content

```http
PUT /_emdash/api/content/:collection/:id
Content-Type: application/json
```

#### Request Body

```json
{
	"data": {
		"title": "Updated Title"
	},
	"status": "published"
}
```

### Delete Content

```http
DELETE /_emdash/api/content/:collection/:id
```

#### Response

```json
{
	"success": true,
	"data": {
		"success": true
	}
}
```

## Media Endpoints

### List Media

```http
GET /_emdash/api/media
```

#### Parameters

| Parameter  | Type     | Description                  |
| ---------- | -------- | ---------------------------- |
| `cursor`   | `string` | Pagination cursor            |
| `limit`    | `number` | Items per page (default: 20) |
| `mimeType` | `string` | Filter by MIME type prefix   |

#### Response

```json
{
	"success": true,
	"data": {
		"items": [
			{
				"id": "01HXK5MZSN...",
				"filename": "photo.jpg",
				"mimeType": "image/jpeg",
				"size": 102400,
				"width": 1920,
				"height": 1080,
				"url": "https://cdn.example.com/photo.jpg",
				"createdAt": "2025-01-24T12:00:00Z"
			}
		],
		"nextCursor": "eyJpZCI6..."
	}
}
```

### Get Media

```http
GET /_emdash/api/media/:id
```

### Create Media

```http
POST /_emdash/api/media
Content-Type: application/json
```

#### Request Body

```json
{
	"filename": "photo.jpg",
	"mimeType": "image/jpeg",
	"size": 102400,
	"width": 1920,
	"height": 1080,
	"storageKey": "uploads/photo.jpg"
}
```

<Aside>
	This endpoint records media metadata after upload. Use the upload endpoint or signed URLs to
	upload the actual file.
</Aside>

### Update Media

```http
PUT /_emdash/api/media/:id
Content-Type: application/json
```

#### Request Body

```json
{
	"alt": "Photo description",
	"caption": "Photo caption"
}
```

### Delete Media

```http
DELETE /_emdash/api/media/:id
```

### Get Media File

```http
GET /_emdash/api/media/file/:key
```

Serves the actual file content. For local storage only.

## Revision Endpoints

### List Revisions

```http
GET /_emdash/api/content/:collection/:entryId/revisions
```

#### Parameters

| Parameter | Type     | Description                           |
| --------- | -------- | ------------------------------------- |
| `limit`   | `number` | Max revisions to return (default: 50) |

#### Response

```json
{
  "success": true,
  "data": {
    "items": [
      {
        "id": "01HXK5MZSN...",
        "collection": "posts",
        "entryId": "01HXK5MZSN...",
        "data": { ... },
        "createdAt": "2025-01-24T12:00:00Z"
      }
    ],
    "total": 5
  }
}
```

### Get Revision

```http
GET /_emdash/api/revisions/:revisionId
```

### Restore Revision

```http
POST /_emdash/api/revisions/:revisionId/restore
```

Restores content to this revision's state and creates a new revision.

## Schema Endpoints

### List Collections

```http
GET /_emdash/api/schema/collections
```

#### Response

```json
{
	"success": true,
	"data": {
		"items": [
			{
				"id": "01HXK5MZSN...",
				"slug": "posts",
				"label": "Posts",
				"labelSingular": "Post",
				"supports": ["drafts", "revisions", "preview"]
			}
		]
	}
}
```

### Get Collection

```http
GET /_emdash/api/schema/collections/:slug
```

#### Parameters

| Parameter       | Type      | Description                       |
| --------------- | --------- | --------------------------------- |
| `includeFields` | `boolean` | Include field definitions (query) |

### Create Collection

```http
POST /_emdash/api/schema/collections
Content-Type: application/json
```

#### Request Body

```json
{
	"slug": "products",
	"label": "Products",
	"labelSingular": "Product",
	"description": "Product catalog",
	"supports": ["drafts", "revisions"]
}
```

### Update Collection

```http
PATCH /_emdash/api/schema/collections/:slug
Content-Type: application/json
```

### Delete Collection

```http
DELETE /_emdash/api/schema/collections/:slug
```

#### Parameters

| Parameter | Type      | Description                                   |
| --------- | --------- | --------------------------------------------- |
| `force`   | `boolean` | Delete even if collection has content (query) |

### List Fields

```http
GET /_emdash/api/schema/collections/:slug/fields
```

### Create Field

```http
POST /_emdash/api/schema/collections/:slug/fields
Content-Type: application/json
```

#### Request Body

```json
{
	"slug": "price",
	"label": "Price",
	"type": "number",
	"required": true,
	"validation": {
		"min": 0
	}
}
```

### Update Field

```http
PATCH /_emdash/api/schema/collections/:collectionSlug/fields/:fieldSlug
Content-Type: application/json
```

### Delete Field

```http
DELETE /_emdash/api/schema/collections/:collectionSlug/fields/:fieldSlug
```

### Reorder Fields

```http
POST /_emdash/api/schema/collections/:slug/fields/reorder
Content-Type: application/json
```

#### Request Body

```json
{
	"fieldSlugs": ["title", "content", "author", "publishedAt"]
}
```

## Schema Export

### Export Schema (JSON)

```http
GET /_emdash/api/schema
Accept: application/json
```

### Export Schema (TypeScript)

```http
GET /_emdash/api/schema?format=typescript
Accept: text/typescript
```

Returns TypeScript interfaces for all collections.

## Plugin Endpoints

### List Plugins

```http
GET /_emdash/api/plugins
```

### Get Plugin

```http
GET /_emdash/api/plugins/:pluginId
```

### Enable Plugin

```http
POST /_emdash/api/plugins/:pluginId/enable
```

### Disable Plugin

```http
POST /_emdash/api/plugins/:pluginId/disable
```

## 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_ERROR`         | 400         | Schema operation failed  |
| `DUPLICATE_SLUG`       | 409         | Slug already exists      |
| `RESERVED_SLUG`        | 400         | Slug is reserved         |

## Search Endpoints

### Global Search

```http
GET /_emdash/api/search?q=hello+world
```

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

```json
{
  "results": [
    {
      "collection": "posts",
      "id": "01HXK5MZSN...",
      "slug": "hello-world",
      "title": "Hello World",
      "snippet": "...this is a <mark>hello</mark> <mark>world</mark> example...",
      "score": 0.95
    }
  ],
  "nextCursor": "eyJvZmZzZXQiOjIwfQ"
}
```

### Search Suggestions

```http
GET /_emdash/api/search/suggest?q=hel&limit=5
```

Returns prefix-matched titles for autocomplete.

### Rebuild Search Index

```http
POST /_emdash/api/search/rebuild
```

Rebuild FTS index for all or specific collections.

### Search Stats

```http
GET /_emdash/api/search/stats
```

Returns indexed document counts per collection.

## Section Endpoints

### List Sections

```http
GET /_emdash/api/sections
GET /_emdash/api/sections?source=theme
GET /_emdash/api/sections?search=newsletter
```

### Get Section

```http
GET /_emdash/api/sections/:slug
```

### Create Section

```http
POST /_emdash/api/sections
Content-Type: application/json

{
  "slug": "my-section",
  "title": "My Section",
  "keywords": ["keyword1"],
  "content": [...]
}
```

### Update Section

```http
PUT /_emdash/api/sections/:slug
```

### Delete Section

```http
DELETE /_emdash/api/sections/:slug
```

## Settings Endpoints

### Get All Settings

```http
GET /_emdash/api/settings
```

### Update Settings

```http
POST /_emdash/api/settings
Content-Type: application/json

{
  "siteTitle": "My Site",
  "tagline": "A great site",
  "postsPerPage": 10
}
```

## Menu Endpoints

### List Menus

```http
GET /_emdash/api/menus
```

### Get Menu

```http
GET /_emdash/api/menus/:name
```

### Create Menu

```http
POST /_emdash/api/menus
Content-Type: application/json

{
  "name": "footer",
  "label": "Footer Navigation"
}
```

### Update Menu

```http
PUT /_emdash/api/menus/:name
```

### Delete Menu

```http
DELETE /_emdash/api/menus/:name
```

### Add Menu Item

```http
POST /_emdash/api/menus/:name/items
Content-Type: application/json

{
  "type": "page",
  "referenceCollection": "pages",
  "referenceId": "page_about",
  "label": "About Us"
}
```

### Reorder Menu Items

```http
POST /_emdash/api/menus/:name/reorder
Content-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

### List Taxonomy Definitions

```http
GET /_emdash/api/taxonomies
```

### Create Taxonomy

```http
POST /_emdash/api/taxonomies
Content-Type: application/json

{
  "name": "genre",
  "label": "Genres",
  "labelSingular": "Genre",
  "hierarchical": true,
  "collections": ["books", "movies"]
}
```

### List Terms

```http
GET /_emdash/api/taxonomies/:name/terms
```

### Create Term

```http
POST /_emdash/api/taxonomies/:name/terms
Content-Type: application/json

{
  "slug": "tutorials",
  "label": "Tutorials",
  "parentId": "term_abc",
  "description": "How-to guides"
}
```

### Update Term

```http
PUT /_emdash/api/taxonomies/:name/terms/:slug
```

### Delete Term

```http
DELETE /_emdash/api/taxonomies/:name/terms/:slug
```

### Set Entry Terms

```http
POST /_emdash/api/content/:collection/:id/terms/:taxonomy
Content-Type: application/json

{
  "termIds": ["term_news", "term_featured"]
}
```

## Widget Area Endpoints

### List Widget Areas

```http
GET /_emdash/api/widget-areas
```

### Get Widget Area

```http
GET /_emdash/api/widget-areas/:name
```

### Create Widget Area

```http
POST /_emdash/api/widget-areas
Content-Type: application/json

{
  "name": "sidebar",
  "label": "Main Sidebar",
  "description": "Appears on posts"
}
```

### Delete Widget Area

```http
DELETE /_emdash/api/widget-areas/:name
```

### Add Widget

```http
POST /_emdash/api/widget-areas/:name/widgets
Content-Type: application/json

{
  "type": "content",
  "title": "About",
  "content": [...]
}
```

### Update Widget

```http
PUT /_emdash/api/widget-areas/:name/widgets/:id
```

### Delete Widget

```http
DELETE /_emdash/api/widget-areas/:name/widgets/:id
```

### Reorder Widgets

```http
POST /_emdash/api/widget-areas/:name/reorder
Content-Type: application/json

{
  "widgetIds": ["widget_1", "widget_2", "widget_3"]
}
```

## User Management Endpoints

### List Users

```http
GET /_emdash/api/admin/users
GET /_emdash/api/admin/users?role=40
GET /_emdash/api/admin/users?search=john
```

### Get User

```http
GET /_emdash/api/admin/users/:id
```

### Update User

```http
PATCH /_emdash/api/admin/users/:id
Content-Type: application/json

{
  "name": "John Doe",
  "role": 40
}
```

### Enable User

```http
POST /_emdash/api/admin/users/:id/enable
```

### Disable User

```http
POST /_emdash/api/admin/users/:id/disable
```

## Authentication Endpoints

### Setup Status

```http
GET /_emdash/api/setup/status
```

Returns whether setup is complete and if users exist.

### Passkey Login

```http
POST /_emdash/api/auth/passkey/options
```

Get WebAuthn authentication options.

```http
POST /_emdash/api/auth/passkey/verify
Content-Type: application/json

{
  "id": "credential-id",
  "rawId": "...",
  "response": {...},
  "type": "public-key"
}
```

Verify passkey and create session.

### Magic Link

```http
POST /_emdash/api/auth/magic-link/send
Content-Type: application/json

{
  "email": "user@example.com"
}
```

```http
GET /_emdash/api/auth/magic-link/verify?token=xxx
```

### Logout

```http
POST /_emdash/api/auth/logout
```

### Current User

```http
GET /_emdash/api/auth/me
```

### Invite User

```http
POST /_emdash/api/auth/invite
Content-Type: application/json

{
  "email": "newuser@example.com",
  "role": 30
}
```

### Passkey Management

```http
GET /_emdash/api/auth/passkey
```

List user's passkeys.

```http
POST /_emdash/api/auth/passkey/register/options
POST /_emdash/api/auth/passkey/register/verify
```

Register new passkey.

```http
PATCH /_emdash/api/auth/passkey/:id
Content-Type: application/json

{
  "name": "MacBook Pro"
}
```

Rename passkey.

```http
DELETE /_emdash/api/auth/passkey/:id
```

Delete passkey.

## Import Endpoints

### Analyze WordPress Export

```http
POST /_emdash/api/import/wordpress/analyze
Content-Type: multipart/form-data

file: <WXR file>
```

### Execute WordPress Import

```http
POST /_emdash/api/import/wordpress/execute
Content-Type: application/json

{
  "analysisId": "...",
  "options": {
    "includeMedia": true,
    "includeTaxonomies": true,
    "includeMenus": true
  }
}
```

## Rate Limiting

API endpoints may be rate-limited based on deployment configuration. When rate-limited, responses include:

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 60
```

## CORS

The API supports CORS for browser requests. Configure allowed origins in your deployment.
