EmDash는 /_emdash/api/에서 REST API를 제공하며, 콘텐츠 관리, 미디어 업로드, 스키마 작업에 사용할 수 있습니다.
인증
API 요청은 Authorization 헤더의 Bearer 토큰을 통한 인증이 필요합니다:
Authorization: Bearer <token>
관리자 인터페이스 또는 프로그래밍 방식으로 토큰을 생성하세요.
응답 형식
모든 응답은 일관된 형식을 따릅니다. 성공 응답은 결과를 data로 래핑합니다:
{
"success": true,
"data": { ... }
}
오류 응답에는 코드, 메시지 및 선택적 세부 정보가 포함됩니다:
{
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable message",
"details": { ... }
}
}
콘텐츠 엔드포인트
콘텐츠 목록
GET /_emdash/api/content/:collection
매개변수
| 매개변수 | 타입 | 설명 |
|---|---|---|
collection | string | 컬렉션 슬러그 (경로) |
cursor | string | 페이지네이션 커서 (쿼리) |
limit | number | 페이지당 항목 수 (쿼리, 기본값: 50) |
status | string | 상태별 필터 (쿼리) |
orderBy | string | 정렬 필드 (쿼리) |
order | string | 정렬 방향: asc 또는 desc (쿼리) |
응답
{
"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 /_emdash/api/content/:collection/:id
응답
{
"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"
}
}
}
콘텐츠 생성
POST /_emdash/api/content/:collection
Content-Type: application/json
요청 본문
{
"data": {
"title": "New Post",
"content": [...]
},
"slug": "new-post",
"status": "draft"
}
응답
{
"success": true,
"data": {
"item": { ... }
}
}
콘텐츠 업데이트
PUT /_emdash/api/content/:collection/:id
Content-Type: application/json
요청 본문
{
"data": {
"title": "Updated Title"
},
"status": "published"
}
콘텐츠 삭제
DELETE /_emdash/api/content/:collection/:id
응답
{
"success": true,
"data": {
"success": true
}
}
미디어 엔드포인트
미디어 목록
GET /_emdash/api/media?includeUsage=1
매개변수
| 매개변수 | 타입 | 설명 |
|---|---|---|
cursor | string | 불투명 페이지네이션 커서 |
limit | number | 페이지당 항목 수, 1~100 (기본값: 50) |
mimeType | string | 쉼표로 구분된 하나 이상의 MIME 타입으로 필터 |
q | string | 대소문자 구분 없는 파일명 검색 |
includeUsage | 1 | 반환되는 각 항목에 커버리지 인식 usage 요약 포함 |
응답
{
"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 /_emdash/api/media/:id?includeUsage=1
includeUsage는 목록과 조회 모두에서 선택사항입니다. 허용되는 유일한 값은 1입니다. 생략하면
usage 속성이 생략되고 서버는 사용량 쿼리를 실행하지 않습니다.
사용량 요약
usage.count는 선택된 현재 인덱싱된 소스가 미디어 항목을 참조하는 EmDash의 고유한 활성 콘텐츠 행 또는 로캘의 수입니다. 동일한 콘텐츠 항목에 대한 반복 참조와 여러 소스 변형은 한 번으로 계산됩니다. 휴지통의 콘텐츠는 계산되지 않습니다.
숫자 카운트는 초안과 유사한 콘텐츠를 드러낼 수 있습니다. 세션 사용자가 content:read_drafts를 가지고 있거나,
API 토큰이 admin 스코프를 가지고 연관된 사용자도 해당 권한을 가진 경우에만 반환됩니다. 다른 미디어 리더는
usage.count: null을 받습니다. 이는 성공적인 수정된 응답이며 오류가 아닙니다.
요청된 각 요약에는 현재 등록된 모든 콘텐츠 컬렉션에 대한 집계 커버리지가 포함됩니다:
| 상태 | 의미 |
|---|---|
complete | 등록된 모든 컬렉션이 최신의 완료된 사용량 커버리지를 가지고 있음 |
never | 등록된 컬렉션 중 초기 사용량 복구를 완료한 것이 없음 |
running | 사용량 복구가 현재 실행 중 |
partial | 커버리지가 혼합되었거나 등록된 범위의 일부만 인덱싱됨 |
failed | 등록된 범위 전체에서 커버리지 실패 |
stale | 인덱싱된 커버리지가 오래됨 |
unknown | 저장된 커버리지에 이 버전이 인식하지 못하는 상태가 포함됨 |
complete만이 아래에 설명된 EmDash 관리 필드 내에서 범위 지정 완전 제로 선언을 지원합니다. 다른 상태의 카운트는 인덱싱된 투영이며 과대 또는 과소 보고할 수 있습니다. 완전한 결과조차도 동시 쓰기 중에는 참고용입니다. 사용량 읽기는 트랜잭션 잠금이 아니며 삭제 보증으로 사용해서는 안 됩니다.
미디어 사용량 상세 조회
GET /_emdash/api/media/:id/usage?limit=50&cursor=...
이 엔드포인트는 media:read와 content:read_drafts가 필요합니다. 토큰 인증 호출자도
admin 스코프가 필요합니다. 토큰 스코프는 연관된 사용자의 권한을 우회하지 않습니다.
limit는 페이지당 콘텐츠 항목 그룹을 제어하며, 1~100입니다 (기본값: 50). 페이지네이션은
반환된 항목 그룹의 소스나 발생을 분할하지 않습니다.
{
"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"
}
}
}
승인된 상세 정보에는 활성 및 삭제된 항목이 포함됩니다. null이 아닌 deletedAt은 휴지통의 항목을 식별합니다. 소스는 columns 또는 draft_overlay이며, 발생은 내부 인덱스 메타데이터를 노출하지 않고 지원되는 필드와 경로를 식별합니다.
미디어 사용량은 EmDash 콘텐츠 컬렉션이 관리하는 최상위 이미지 및 파일 필드, 리피터 이미지 필드, Portable Text 이미지 블록의 로컬 미디어 참조를 포함합니다. 커스텀 코드, 렌더링된 HTML, 설정, 메뉴, 위젯, 플러그인 비공개 데이터, 외부 사이트 또는 프로바이더 전용 자산은 스캔하지 않습니다.
미디어 생성
POST /_emdash/api/media
Content-Type: application/json
요청 본문
{
"filename": "photo.jpg",
"mimeType": "image/jpeg",
"size": 102400,
"width": 1920,
"height": 1080,
"storageKey": "uploads/photo.jpg"
}
미디어 업데이트
PUT /_emdash/api/media/:id
Content-Type: application/json
요청 본문
{
"alt": "Photo description",
"caption": "Photo caption"
}
미디어 삭제
DELETE /_emdash/api/media/:id
미디어 사용량 복구
POST /_emdash/api/admin/media-usage/repair
Content-Type: application/json
X-EmDash-Request: 1
하나의 컬렉션 또는 모든 콘텐츠 컬렉션의 콘텐츠 미디어 사용량 인덱스를 복구합니다. 이것은 관리자/운영자 엔드포인트입니다: 세션 인증 호출자는 schema:manage가 필요하고, 라우트가 /_emdash/api/admin 아래에 있기 때문에 Bearer 토큰은 admin 스코프가 필요합니다.
전체 콘텐츠 복구는 현재 버전에서 동기적이고 순차적으로 실행됩니다. 대규모 사이트에서는 비용이 많이 들 수 있으므로, 호출자는 의도적으로 트리거하고 응답을 기다려야 합니다.
요청 본문
하나의 컬렉션 복구:
{
"scope": "collection",
"collection": "posts"
}
모든 콘텐츠 컬렉션 복구:
{
"scope": "all"
}
요청 본문은 필수입니다. 유효하지 않은 슬러그, 알 수 없는 요청 키, 누락된 scope, 본문 없는 요청은 전체 콘텐츠 복구를 기본값으로 하는 대신 400을 반환합니다.
응답
복구 호출이 구조화된 결과를 생성하면 엔드포인트는 200을 반환합니다. data.status를 확인하세요: failed와 stale은 복구 도메인 상태이며 전송 오류가 아닙니다.
{
"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"
}
]
}
}
최상위 응답 필드:
| 필드 | 타입 | 설명 |
|---|---|---|
status | complete | partial | failed | stale | 집계 복구 상태 |
indexedSourceCount | number | 복구 중 인덱싱된 소스 |
failedSourceCount | number | 복구 중 실패한 소스 |
skippedSourceCount | number | 건너뛴 소스 (오래된 충돌 포함) |
deletedSourceCount | number | 복구 중 삭제된 오래된 사용량 행 |
collections | array | 컬렉션별 복구 요약 |
컬렉션 요약 필드:
| 필드 | 타입 | 설명 |
|---|---|---|
collection | string | 컬렉션 슬러그 |
status | complete | partial | failed | stale | 컬렉션 복구 상태 |
indexedSourceCount | number | 이 컬렉션에서 인덱싱된 소스 |
failedSourceCount | number | 이 컬렉션에서 실패한 소스 |
skippedSourceCount | number | 이 컬렉션에서 건너뛴 소스 |
deletedSourceCount | number | 이 컬렉션에서 삭제된 오래된 사용량 행 |
lastErrorCode | string | null | 마지막 컬렉션 복구 오류 (사용 가능한 경우) |
startedAt | string | 복구 시작 시간 |
completedAt | string | null | 완료 시간, 또는 오래된 결과의 경우 null |
알 수 없는 컬렉션은 200을 반환하며 data.status: "failed"와 COLLECTION_NOT_FOUND와 같은 컬렉션별 lastErrorCode가 포함됩니다. 전송 오류는 400, 401, 403, 413, 500을 포함한 표준 오류 봉투를 계속 사용합니다.
미디어 파일 조회
GET /_emdash/api/media/file/:key
실제 파일 콘텐츠를 제공합니다. 로컬 스토리지 전용.
리비전 엔드포인트
리비전 목록
GET /_emdash/api/content/:collection/:entryId/revisions
매개변수
| 매개변수 | 타입 | 설명 |
|---|---|---|
limit | number | 반환할 최대 리비전 수 (기본값: 50) |
응답
{
"success": true,
"data": {
"items": [
{
"id": "01HXK5MZSN...",
"collection": "posts",
"entryId": "01HXK5MZSN...",
"data": { ... },
"createdAt": "2025-01-24T12:00:00Z"
}
],
"total": 5
}
}
리비전 조회
GET /_emdash/api/revisions/:revisionId
리비전 복원
POST /_emdash/api/revisions/:revisionId/restore
콘텐츠를 이 리비전의 상태로 복원하고 새 리비전을 생성합니다.
스키마 엔드포인트
컬렉션 목록
GET /_emdash/api/schema/collections
응답
{
"success": true,
"data": {
"items": [
{
"id": "01HXK5MZSN...",
"slug": "posts",
"label": "Posts",
"labelSingular": "Post",
"supports": ["drafts", "revisions", "preview"]
}
]
}
}
컬렉션 조회
GET /_emdash/api/schema/collections/:slug
매개변수
| 매개변수 | 타입 | 설명 |
|---|---|---|
includeFields | boolean | 필드 정의 포함 (쿼리) |
컬렉션 생성
POST /_emdash/api/schema/collections
Content-Type: application/json
요청 본문
{
"slug": "products",
"label": "Products",
"labelSingular": "Product",
"description": "Product catalog",
"supports": ["drafts", "revisions"]
}
컬렉션 업데이트
PUT /_emdash/api/schema/collections/:slug
Content-Type: application/json
컬렉션 삭제
DELETE /_emdash/api/schema/collections/:slug
매개변수
| 매개변수 | 타입 | 설명 |
|---|---|---|
force | boolean | 컬렉션에 콘텐츠가 있어도 삭제 (쿼리) |
필드 목록
GET /_emdash/api/schema/collections/:slug/fields
필드 생성
POST /_emdash/api/schema/collections/:slug/fields
Content-Type: application/json
요청 본문
{
"slug": "price",
"label": "Price",
"type": "number",
"required": true,
"validation": {
"min": 0
}
}
필드 업데이트
PUT /_emdash/api/schema/collections/:collectionSlug/fields/:fieldSlug
Content-Type: application/json
필드 삭제
DELETE /_emdash/api/schema/collections/:collectionSlug/fields/:fieldSlug
필드 재정렬
POST /_emdash/api/schema/collections/:slug/fields/reorder
Content-Type: application/json
요청 본문
{
"fieldSlugs": ["title", "content", "author", "publishedAt"]
}
스키마 내보내기
스키마 내보내기 (JSON)
GET /_emdash/api/schema
Accept: application/json
스키마 내보내기 (TypeScript)
GET /_emdash/api/schema?format=typescript
Accept: text/typescript
모든 컬렉션의 TypeScript 인터페이스를 반환합니다.
플러그인 엔드포인트
플러그인 목록
GET /_emdash/api/admin/plugins
플러그인 조회
GET /_emdash/api/admin/plugins/:id
플러그인 활성화
POST /_emdash/api/admin/plugins/:id/enable
플러그인 비활성화
POST /_emdash/api/admin/plugins/:id/disable
오류 코드
| 코드 | HTTP 상태 | 설명 |
|---|---|---|
NOT_FOUND | 404 | 리소스를 찾을 수 없음 |
VALIDATION_ERROR | 400 | 잘못된 입력 데이터 |
UNAUTHORIZED | 401 | 토큰 누락 또는 무효 |
FORBIDDEN | 403 | 권한 부족 |
CONTENT_LIST_ERROR | 500 | 콘텐츠 목록 조회 실패 |
CONTENT_CREATE_ERROR | 500 | 콘텐츠 생성 실패 |
CONTENT_UPDATE_ERROR | 500 | 콘텐츠 업데이트 실패 |
CONTENT_DELETE_ERROR | 500 | 콘텐츠 삭제 실패 |
MEDIA_LIST_ERROR | 500 | 미디어 목록 조회 실패 |
MEDIA_CREATE_ERROR | 500 | 미디어 생성 실패 |
SCHEMA_CREATE_ERROR | 500 | 스키마 작업 실패 |
SLUG_CONFLICT | 409 | 슬러그가 이미 존재함 |
RESERVED_SLUG | 400 | 슬러그가 예약됨 |
검색 엔드포인트
글로벌 검색
GET /_emdash/api/search?q=hello+world
매개변수
| 매개변수 | 타입 | 설명 |
|---|---|---|
q | string | 검색 쿼리 (필수) |
collections | string | 쉼표로 구분된 컬렉션 슬러그 |
status | string | 상태별 필터 (기본값: published) |
limit | number | 최대 결과 수 (기본값: 20) |
cursor | string | 페이지네이션 커서 |
응답
{
"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"
}
}
검색 제안
GET /_emdash/api/search/suggest?q=hel&limit=5
자동 완성을 위한 접두사 일치 제목을 반환합니다.
검색 인덱스 재구축
POST /_emdash/api/search/rebuild
모든 또는 특정 컬렉션의 FTS 인덱스를 재구축합니다.
검색 통계
GET /_emdash/api/search/stats
컬렉션별 인덱싱된 문서 수를 반환합니다.
섹션 엔드포인트
섹션 목록
GET /_emdash/api/sections
GET /_emdash/api/sections?source=theme
GET /_emdash/api/sections?search=newsletter
섹션 조회
GET /_emdash/api/sections/:slug
섹션 생성
POST /_emdash/api/sections
Content-Type: application/json
{
"slug": "my-section",
"title": "My Section",
"keywords": ["keyword1"],
"content": [...]
}
섹션 업데이트
PUT /_emdash/api/sections/:slug
섹션 삭제
DELETE /_emdash/api/sections/:slug
설정 엔드포인트
모든 설정 조회
GET /_emdash/api/settings
설정 업데이트
POST /_emdash/api/settings
Content-Type: application/json
{
"siteTitle": "My Site",
"tagline": "A great site",
"postsPerPage": 10
}
메뉴 엔드포인트
메뉴 목록
GET /_emdash/api/menus
메뉴 조회
GET /_emdash/api/menus/:name
메뉴 생성
POST /_emdash/api/menus
Content-Type: application/json
{
"name": "main",
"label": "Main Navigation",
"items": []
}
메뉴 업데이트
PUT /_emdash/api/menus/:name
메뉴 삭제
DELETE /_emdash/api/menus/:name
메뉴 항목 추가
POST /_emdash/api/menus/:name/items
Content-Type: application/json
{
"label": "About",
"url": "/about",
"position": 0
}
메뉴 항목 재정렬
POST /_emdash/api/menus/:name/reorder
Content-Type: application/json
{
"itemIds": ["item_1", "item_2", "item_3"]
}
택소노미 엔드포인트
택소노미 정의 목록
GET /_emdash/api/taxonomies
택소노미 생성
POST /_emdash/api/taxonomies
Content-Type: application/json
{
"name": "categories",
"label": "Categories",
"hierarchical": true,
"collections": ["posts"]
}
용어 목록
GET /_emdash/api/taxonomies/:name/terms
용어 생성
POST /_emdash/api/taxonomies/:name/terms
Content-Type: application/json
{
"slug": "tutorials",
"label": "Tutorials",
"parentId": "term_abc",
"description": "How-to guides"
}
용어 업데이트
PUT /_emdash/api/taxonomies/:name/terms/:slug
용어 삭제
DELETE /_emdash/api/taxonomies/:name/terms/:slug
항목 용어 설정
POST /_emdash/api/content/:collection/:id/terms/:taxonomy
Content-Type: application/json
{
"termIds": ["term_news", "term_featured"]
}
위젯 영역 엔드포인트
위젯 영역 목록
GET /_emdash/api/widget-areas
위젯 영역 조회
GET /_emdash/api/widget-areas/:name
위젯 영역 생성
POST /_emdash/api/widget-areas
Content-Type: application/json
{
"name": "sidebar",
"label": "Main Sidebar",
"description": "Appears on posts"
}
위젯 영역 삭제
DELETE /_emdash/api/widget-areas/:name
위젯 추가
POST /_emdash/api/widget-areas/:name/widgets
Content-Type: application/json
{
"type": "content",
"title": "About",
"content": [...]
}
위젯 업데이트
PUT /_emdash/api/widget-areas/:name/widgets/:id
위젯 삭제
DELETE /_emdash/api/widget-areas/:name/widgets/:id
위젯 재정렬
POST /_emdash/api/widget-areas/:name/reorder
Content-Type: application/json
{
"widgetIds": ["widget_1", "widget_2", "widget_3"]
}
사용자 관리 엔드포인트
사용자 목록
GET /_emdash/api/admin/users
GET /_emdash/api/admin/users?role=40
GET /_emdash/api/admin/users?search=john
사용자 조회
GET /_emdash/api/admin/users/:id
사용자 업데이트
PUT /_emdash/api/admin/users/:id
Content-Type: application/json
{
"name": "John Doe",
"role": 40
}
사용자 활성화
POST /_emdash/api/admin/users/:id/enable
사용자 비활성화
POST /_emdash/api/admin/users/:id/disable
인증 엔드포인트
설정 상태
GET /_emdash/api/setup/status
설정이 완료되었는지와 사용자가 존재하는지를 반환합니다.
Passkey 로그인
POST /_emdash/api/auth/passkey/options
WebAuthn 인증 옵션을 가져옵니다.
POST /_emdash/api/auth/passkey/verify
Content-Type: application/json
{
"id": "credential-id",
"rawId": "...",
"response": {...},
"type": "public-key"
}
Passkey를 검증하고 세션을 생성합니다.
매직 링크
POST /_emdash/api/auth/magic-link/send
Content-Type: application/json
{
"email": "[email protected]"
}
GET /_emdash/api/auth/magic-link/verify?token=xxx
로그아웃
POST /_emdash/api/auth/logout
현재 사용자
GET /_emdash/api/auth/me
사용자 초대
POST /_emdash/api/auth/invite
Content-Type: application/json
{
"email": "[email protected]",
"role": 30
}
Passkey 관리
GET /_emdash/api/auth/passkey
사용자의 Passkey를 목록으로 표시합니다.
POST /_emdash/api/auth/passkey/register/options
POST /_emdash/api/auth/passkey/register/verify
새 Passkey를 등록합니다.
PATCH /_emdash/api/auth/passkey/:id
Content-Type: application/json
{
"name": "MacBook Pro"
}
Passkey 이름을 변경합니다.
DELETE /_emdash/api/auth/passkey/:id
Passkey를 삭제합니다.
가져오기 엔드포인트
WordPress 내보내기 분석
POST /_emdash/api/import/wordpress/analyze
Content-Type: multipart/form-data
file: <WXR file>
WordPress 가져오기 실행
POST /_emdash/api/import/wordpress/execute
Content-Type: application/json
{
"analysisId": "...",
"options": {
"includeMedia": true,
"includeTaxonomies": true,
"includeMenus": true
}
}
속도 제한
API 엔드포인트는 배포 구성에 따라 속도 제한이 적용될 수 있습니다. 속도 제한 시 응답에는 다음이 포함됩니다:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
CORS
API는 브라우저 요청을 위한 CORS를 지원합니다. 배포에서 허용된 오리진을 구성하세요.