DEVELOPERS
Postman · Insomnia · AI 에이전트 도구에는 https://darman-pub.azurewebsites.net/openapi.json 주소를 그대로 넣으면 됩니다.
다르만 출판사 API 가이드
외부 시스템과 AI 에이전트가 소식지 서비스를 그대로 다룰 수 있도록 만든 연동 안내서입니다.
글을 올리고, 재작성 지시를 받아 고쳐 올리고, 컨펌해서 게시·발송하는 전 과정을 API로 처리할 수 있습니다.
- 기계가 읽는 명세(OpenAPI 3.1):
https://darman-pub.azurewebsites.net/openapi.json - 이 문서 내려받기:
https://darman-pub.azurewebsites.net/api-guide.md - 기준 주소:
https://darman-pub.azurewebsites.net
1. 빠른 시작
1) 키 발급
관리자 화면 → 설정 → 외부 연동 API 키에서 이름과 권한을 고르고 발급합니다.
키는 발급 순간 한 번만 표시되니 바로 안전한 곳에 보관하세요. 형식은 dmk_ 로 시작합니다.
2) 첫 호출
curl https://darman-pub.azurewebsites.net/api/v1/categories \
-H "Authorization: Bearer $DARMAN_API_KEY"
{
"ok": true,
"items": [
{ "id": 1, "slug": "buddhist-tales", "name": "불교 야화", "next_series_no": 1001 },
{ "id": 2, "slug": "healing", "name": "힐링 메시지", "next_series_no": 1 }
]
}
3) 글 올리기
curl -X POST https://darman-pub.azurewebsites.net/api/v1/posts \
-H "Authorization: Bearer $DARMAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"category":"healing","title":"오늘의 한 문장","body_md":"## 한 줄 요약\n오늘은 이만큼만.","tags":["위로"]}'
응답에 검토 링크와 웹훅 주소가 함께 옵니다. 사람이 컨펌하면 게시됩니다.
2. 인증
모든 /api/v1/* 요청에 키를 넣습니다. 세션이나 CSRF 토큰은 필요 없습니다.
Authorization: Bearer dmk_xxxxxxxxxxxxxxxxxxxx
X-API-Key: dmk_... 헤더도 같은 뜻으로 받습니다. 키가 없거나 폐기됐으면 401, 권한이 모자라면 403 이 돌아옵니다.
권한(scope)
| scope | 할 수 있는 일 |
|---|---|
content:read | 주제·글 목록·상세 조회, 검토 큐 읽기 |
content:write | 글 올리기·수정 (재작성 결과 재업로드 포함) |
review:write | 컨펌·재작성 지시·즉시 게시·검토 요청 메일 발송 |
admin:read | 통계·발송 내역·설정 조회 |
admin:write | 설정 변경·글 삭제·재발송·발송 작업 실행 |
집필 에이전트에는 보통 content:read, content:write 만 주고, 컨펌은 사람이 하도록 두는 편이 안전합니다.
3. 글의 상태
| 상태 | 뜻 | 공개 | 메일 |
|---|---|---|---|
pending | 컨펌(검토 승인) 대기 | 안 됨 | 안 나감 |
rewrite | 재작성 요청됨 (사유가 review_note 에) | 안 됨 | 안 나감 |
scheduled | 컨펌됐고 예약 시각 대기 | 안 됨 | 그 시각에 |
published | 게시됨 | 공개 | 발송됨 |
draft | 초안 | 안 됨 | 안 나감 |
archived | 보관 | 안 됨 | 안 나감 |
컨펌 게이트(설정 review_required, 기본 켜짐)가 켜져 있으면 새로 올라온 글은 무조건 pending 으로 들어옵니다.
컨펌하면 벌어지는 일
- 예약 시각(
publish_at)이 미래면 →scheduled로 두고 그 시각에 게시·발송합니다. - 예약 시각이 없거나 지났으면 → 즉시 게시하고 구독자 발송 큐를 적재합니다.
- 전에 게시된 적 있는 글이면 → 원래 게시 시각을 유지하고 메일을 다시 보내지 않습니다.
4. AI 에이전트 연동
집필 에이전트가 글을 올리고, 사람이 재작성을 지시하면 고쳐서 다시 올리는 순환입니다.
1단계 — 글 올리기
POST /api/v1/posts · content:write
{
"category": "healing",
"slug": "healing-today-one-line",
"title": "오늘의 한 문장",
"summary": "비우면 본문에서 자동 생성됩니다",
"body_md": "## 한 줄 요약\n오늘은 이만큼만 하면 됩니다.",
"tags": ["위로", "아침"]
}
응답:
{
"ok": true,
"created": true,
"queued": 0,
"review_required": true,
"post": {
"id": 4123,
"slug": "healing-today-one-line",
"status": "pending",
"review_url": "https://darman-pub.azurewebsites.net/review/AbCd...",
"confirm_webhook": "https://darman-pub.azurewebsites.net/api/review/AbCd.../confirm",
"rewrite_webhook": "https://darman-pub.azurewebsites.net/api/review/AbCd.../rewrite"
}
}
slug 를 직접 정해 두면 나중에 같은 글을 덮어쓰기 쉽습니다. 비우면 제목에서 자동 생성됩니다(한글 제목은 주제-날짜 형태).
2단계 — 재작성 지시 받기
GET /api/v1/review/queue?status=rewrite · content:read
curl "https://darman-pub.azurewebsites.net/api/v1/review/queue?status=rewrite&body=1" \
-H "Authorization: Bearer $DARMAN_API_KEY"
{
"ok": true,
"status": "rewrite",
"total": 2,
"items": [
{
"id": 4123,
"slug": "healing-today-one-line",
"title": "오늘의 한 문장",
"review_note": "도입부를 더 짧게, 예시를 하나 추가",
"body_md": "## 한 줄 요약\n..."
}
]
}
review_note 가 사람이 남긴 재작성 사유입니다. body=1 을 붙이면 본문까지 같이 받습니다.
3단계 — 고쳐서 다시 올리기
같은 slug 로 POST /api/v1/posts 를 호출하면 덮어쓰고 상태가 다시 pending 이 됩니다.
{
"slug": "healing-today-one-line",
"category": "healing",
"title": "오늘의 한 문장",
"body_md": "## 한 줄 요약\n짧게 고쳤습니다.\n\n예시를 하나 더했습니다."
}
응답의 created 가 false 면 기존 글을 덮어쓴 것입니다.
4단계 — 컨펌
사람이 검토 링크나 메일에서 눌러도 되고, 권한이 있으면 API로도 가능합니다.
curl -X POST https://darman-pub.azurewebsites.net/api/v1/posts/4123/confirm \
-H "Authorization: Bearer $DARMAN_API_KEY"
{ "ok": true, "result": "published", "queued": 12, "post": { "status": "published" } }
result 는 published · scheduled · restored · already_published 중 하나이고, queued 는 적재된 메일 발송 건수입니다.
메일을 보내지 않고 게시만 하려면 {"notify": false} 를 함께 보냅니다.
에이전트 의사코드
매일:
주제마다:
글 = 생성한다()
POST /api/v1/posts { slug, category, title, body_md }
주기적으로:
큐 = GET /api/v1/review/queue?status=rewrite&body=1
큐의 각 글에 대해:
새본문 = 다시쓴다(글.body_md, 글.review_note)
POST /api/v1/posts { slug: 글.slug, category: 글.category, title: 글.title, body_md: 새본문 }
5. 이미지 넣기
글에 사진을 쓰려면 먼저 이미지를 올리고, 돌려받은 url 을 본문이나 대표 이미지에 씁니다.
올리기 — 두 가지 방법
바이트를 그대로 (가장 간단합니다)
curl -X POST "https://darman-pub.azurewebsites.net/api/v1/images?alt=%EA%B0%80%EC%9D%84%20%EB%93%A4%ED%8C%90" \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: image/jpeg" \
--data-binary @photo.jpg
JSON 으로 base64
POST https://darman-pub.azurewebsites.net/api/v1/images
Authorization: Bearer <키>
Content-Type: application/json
{ "data_base64": "/9j/4AAQSkZJRg...", "alt": "가을 들판" }
data:image/jpeg;base64, 접두사가 붙어 있어도 알아서 떼어냅니다.
응답
{
"ok": true,
"image": {
"id": "9f2c…",
"url": "/uploads/9f2c….jpg",
"absolute_url": "https://darman-pub.azurewebsites.net/uploads/9f2c….jpg",
"mime": "image/jpeg", "bytes": 214880, "width": 1600, "height": 1067,
"alt": "가을 들판", "deduped": false
}
}
deduped: true 는 똑같은 사진이 이미 있어서 그것을 그대로 돌려줬다는 뜻입니다. 같은 파일을 여러 번 올려도 저장 공간이 늘지 않습니다.
글에 쓰기
본문 안에 넣을 때는 마크다운 이미지 문법에 url 을 그대로 씁니다.
{
"category": "healing-art",
"title": "가을 들판을 그린 사람들",
"cover_image": "/uploads/9f2c….jpg",
"cover_alt": "가을 들판",
"body_md": "## 첫 장면\n\n\n\n그림은 이렇게 시작합니다…"
}
cover_image— 대표 이미지. 목록 카드, 메일, 카카오톡·슬랙 공유 미리보기(og:image), RSS 에 쓰입니다. 빈 문자열""을 보내면 해제됩니다.- 본문 이미지 — 업로드한
/uploads/…주소이거나https://로 시작하는 외부 주소만 그림으로 그려집니다. 그 밖의 주소는 무시합니다.
제한과 거절 사유
| 항목 | 값 |
|---|---|
| 형식 | JPEG · PNG · GIF · WebP |
| 한 장 용량 | 6MB |
| 가로·세로 | 각 12,000px, 합쳐서 4천만 화소 |
| 받지 않는 것 | SVG (스크립트를 담을 수 있어 제외), 그 밖의 모든 형식 |
형식은 파일 앞부분 바이트로 판별합니다. 확장자나 Content-Type 을 바꿔 보내도 통하지 않습니다.
| 코드 | 뜻 |
|---|---|
413 | 용량·해상도 초과 |
415 | 허용하지 않는 형식 (SVG 포함) |
409 | 삭제하려는 이미지를 쓰는 글이 있음 (?force=1 로 강행) |
목록·삭제
GET https://darman-pub.azurewebsites.net/api/v1/images?page=1 content:read
GET https://darman-pub.azurewebsites.net/api/v1/images/{id} content:read
DELETE https://darman-pub.azurewebsites.net/api/v1/images/{id} admin:write
6. 사이트 고치기 — 문구 · 메뉴 · 페이지 · 배너
화면에 나가는 문구, 메뉴, 고정 페이지, 배너(광고)를 배포 없이 바꿀 수 있습니다. 관리자 화면에서 하는 일을 그대로 API 로도 할 수 있습니다.
화면 문구
GET https://darman-pub.azurewebsites.net/api/v1/site admin:read
PATCH https://darman-pub.azurewebsites.net/api/v1/site admin:write
GET 은 현재 값과 함께 각 항목의 기본값·최대 길이·형식을 알려줍니다.
{
"ok": true,
"values": { "hero_title": "매일 한 편,\n마음과 일을 위한 짧은 읽을거리", "footer_legal": "…" },
"fields": { "hero_title": { "default": "…", "maxLength": 160, "kind": "lines" } }
}
kind | 뜻 |
|---|---|
plain | 글자 그대로 |
rich | 굵게 와 링크 를 쓸 수 있음 |
lines | 줄바꿈이 화면에서도 줄바꿈으로 |
email | 이메일 형식 검사 |
adsense | ca-pub- 로 시작하는 게시자 ID |
PATCH 는 보낸 키만 바꿉니다. 빈 문자열을 보내면 기본 문구로 되돌아갑니다.
PATCH https://darman-pub.azurewebsites.net/api/v1/site
{ "hero_title": "매일 한 편,\n오늘의 그림과 이야기", "footer_legal": "정보 제공 목적이며 투자 권유가 아닙니다." }
어떤 값을 보내도 HTML 로 해석되지 않습니다. 태그를 넣으면 글자 그대로 보입니다.
메뉴
GET https://darman-pub.azurewebsites.net/api/v1/menu admin:read
POST https://darman-pub.azurewebsites.net/api/v1/menu admin:write
PATCH https://darman-pub.azurewebsites.net/api/v1/menu/{id} admin:write
DELETE https://darman-pub.azurewebsites.net/api/v1/menu/{id} admin:write
area 는 header(상단), footer_browse·footer_subscribe·footer_about(푸터 세 칸) 중 하나입니다. 주소는 / 로 시작하는 사이트 내 경로이거나 http(s)·mailto 만 받습니다.
고정 페이지
GET https://darman-pub.azurewebsites.net/api/v1/pages content:read
POST https://darman-pub.azurewebsites.net/api/v1/pages admin:write
PATCH https://darman-pub.azurewebsites.net/api/v1/pages/{id} admin:write
DELETE https://darman-pub.azurewebsites.net/api/v1/pages/{id} admin:write
slug 를 about · privacy · terms 로 만들면 기본 화면을 대신합니다. 지우면 기본 화면이 다시 나갑니다. 그 밖의 주소는 /page/{slug} 로 열리고, in_menu: true 면 푸터에 링크가 붙습니다. 본문은 마크다운입니다.
배너 · 광고
GET https://darman-pub.azurewebsites.net/api/v1/banners admin:read (노출·클릭 수 포함)
POST https://darman-pub.azurewebsites.net/api/v1/banners admin:write
PATCH https://darman-pub.azurewebsites.net/api/v1/banners/{id} admin:write
DELETE https://darman-pub.azurewebsites.net/api/v1/banners/{id} admin:write
노출 위치(slot)
| 값 | 자리 |
|---|---|
topbar | 헤더 바로 아래 (공지 띠) |
home_hero | 홈 · 히어로 아래 |
home_mid | 홈 · 주제별 글과 최신 글 사이 |
list_top | 목록·주제 화면 상단 |
post_top | 글 · 본문 시작 전 |
post_bottom | 글 · 본문 끝 |
footer_top | 푸터 바로 위 |
종류(kind) — text(제목·설명·버튼 카드), image(업로드한 이미지), notice(한 줄 공지, topbar 전용), adsense
POST https://darman-pub.azurewebsites.net/api/v1/banners
{
"slot": "home_mid", "kind": "text", "name": "가을 이벤트",
"title": "**가을 미술 기획전** 초대",
"body": "미술 이야기 구독자에게 드리는 무료 관람권.",
"cta_label": "신청하기", "url": "https://example.com/event",
"category": "healing-art", "audience": "member",
"starts_at": "2026-10-01T00:00:00+09:00", "ends_at": "2026-10-31T23:59:59+09:00",
"weight": 10
}
audience—all·guest(비회원만) ·member(회원만)category— 그 주제의 글·목록 화면에서만 노출starts_at·ends_at— 비우면 무기한. 같은 자리에 여럿이면weight가 큰 쪽이 먼저- 링크는
/b/{id}로 감싸 클릭이 집계되고, 바깥 주소에는rel="sponsored nofollow noopener"가 붙습니다 - 이미지는
POST /api/v1/images로 올린/uploads/…만 씁니다
애드센스
/api/v1/site 의 adsense_client 에 게시자 ID(ca-pub-…)를 넣으면 그때부터:
- 광고 스크립트가 페이지에 실리고
- 보안 정책(CSP)에 구글 광고 도메인이 열리며
https://darman-pub.azurewebsites.net/ads.txt가 자동으로 만들어집니다
비워두면 이 중 아무것도 일어나지 않습니다. 광고를 켜도 인라인 스크립트는 쓰지 않으므로 script-src 에 unsafe-inline 이 열리지는 않습니다.
광고 단위는 kind: "adsense" 배너로 만들고, 애드센스에서 발급한 광고 단위의 data-ad-slot 값을 ad_slot 에 넣습니다.
7. 언어별 사이트
한 서버에서 언어마다 독립된 사이트가 섭니다. 주소는 언어별 경로로 나뉩니다.
| 언어 | 주소 | 시간대 |
|---|---|---|
| 한국어 (기본) | https://darman-pub.azurewebsites.net/ | Asia/Seoul |
| 일본어 | https://darman-pub.azurewebsites.net/ja/ | Asia/Tokyo |
| 영어 | https://darman-pub.azurewebsites.net/en/ | America/New_York |
| 독일어 | https://darman-pub.azurewebsites.net/de/ | Europe/Berlin |
실제로 열려 있는 언어는 GET /api/v1/categories 응답의 locales 로 확인할 수 있습니다.
글의 언어는 주제가 정합니다
글을 올릴 때 언어를 따로 지정하지 않습니다. 주제(category)가 어느 언어에 속하는지가 곧 글의 언어입니다.
POST https://darman-pub.azurewebsites.net/api/v1/posts
{ "category": "zen", "title": "古い寺の朝", "body_md": "…" }
zen 이 일본어 주제라면 이 글은 일본어 사이트에 올라가고, 주소는 https://darman-pub.azurewebsites.net/ja/p/… 가 됩니다. 응답의 locale 과 url 로 확인하세요.
목록을 언어로 거르기
GET https://darman-pub.azurewebsites.net/api/v1/categories?locale=ja
GET https://darman-pub.azurewebsites.net/api/v1/posts?locale=ja&status=pending
GET https://darman-pub.azurewebsites.net/api/v1/review/queue?locale=ja
locale 을 빼면 모든 언어가 섞여 나옵니다. 에이전트가 한 언어만 맡는다면 항상 붙이는 편이 안전합니다.
문구·메뉴·페이지·배너
사이트 구성도 언어별입니다.
GET https://darman-pub.azurewebsites.net/api/v1/site?locale=ja
PATCH https://darman-pub.azurewebsites.net/api/v1/site?locale=ja { "hero_title": "毎日一篇、…" }
GET https://darman-pub.azurewebsites.net/api/v1/menu?locale=ja
POST https://darman-pub.azurewebsites.net/api/v1/pages { "locale": "ja", "slug": "about", … }
POST https://darman-pub.azurewebsites.net/api/v1/banners { "locale": "ja", "slot": "home_mid", … }
배너의 locale 에 "*" 를 주면 모든 언어에 노출됩니다(기본값).
메일
구독자에게 나가는 메일은 그 회원의 언어로 만들어집니다. 제목·본문·버튼·수신거부 안내와 본문 링크(/ja/p/…)까지 그 언어를 따릅니다. 발송 시각도 회원이 사는 시간대를 기준으로 판단합니다.
8. 사람이 검토하는 방법
검토 링크
글마다 발급되는 주소로, 로그인 없이 열립니다. 메일·슬랙·사내 도구에 그대로 붙이면 됩니다.
https://darman-pub.azurewebsites.net/review/{token}
내용을 읽고 컨펌하고 게시 또는 재작성 요청을 누르면 끝입니다. 토큰이 곧 열쇠이므로 외부에 공유하지 마세요.
검토 요청 메일
대기 중인 글을 묶어 관리자에게 보냅니다. 메일 안의 버튼을 누르면 해당 동작이 선택된 검토 화면이 열립니다.
curl -X POST https://darman-pub.azurewebsites.net/api/v1/review/notify \
-H "Authorization: Bearer $DARMAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"limit":10}'
특정 주소로만 보내려면 {"to":"editor@example.com"} 을 함께 보냅니다.
설정에서 검토 대기 글이 생기면 관리자에게 메일을 켜 두면 글이 올라올 때 자동으로 나갑니다.
메일의 버튼은 클릭 즉시 게시하지 않고 확인 화면을 한 번 거칩니다. 메일 보안 스캐너가 링크를 자동으로 열어 의도치 않게 게시되는 사고를 막기 위한 설계입니다.
9. 검토 웹훅 (인증 불필요)
토큰 자체가 인증 수단이라 외부 시스템에서 바로 호출할 수 있습니다.
| 메서드 | 주소 | 하는 일 |
|---|---|---|
POST | /api/review/{token}/confirm | 컨펌 → 게시 |
POST | /api/review/{token}/rewrite | 재작성 지시 ({"note":"사유"}) |
GET | /api/review/{token} | 상태 조회 |
curl -X POST https://darman-pub.azurewebsites.net/api/review/AbCd.../rewrite \
-H "Content-Type: application/json" \
-d '{"note":"표의 숫자 출처를 밝혀 주세요","by":"편집자 김"}'
by 를 넣으면 누가 처리했는지 감사기록에 남습니다.
10. 발송 동작
구독자는 주제마다 즉시 수신 또는 하루 한 번 모아 보기(다이제스트) 를 고를 수 있습니다.
- 컨펌으로 게시되는 순간, 그 주제를 즉시 수신으로 둔 구독자에게 발송 큐가 적재됩니다.
- 다이제스트 구독자는 각자 정한 시각에 그날 글을 묶어 받습니다.
- 예약(
publish_at)된 글은 그 시각에 게시되며 같은 규칙으로 발송됩니다. - 발송 작업을 지금 돌리려면
POST /api/v1/newsletter/tick(admin:write).
11. 엔드포인트 요약
| 메서드 | 경로 | 권한 | 설명 |
|---|---|---|---|
GET | /api/v1/categories | content:read | 주제 목록 (다음 연재 번호 포함) |
GET | /api/v1/posts | content:read | 글 목록 (status·category·q·page·limit) |
POST | /api/v1/posts | content:write | 글 올리기 (같은 slug 면 덮어쓰기) |
GET | /api/v1/posts/{id} | content:read | 글 상세 (본문 포함) |
PATCH | /api/v1/posts/{id} | content:write | 글 수정 |
DELETE | /api/v1/posts/{id} | admin:write | 글 삭제 |
POST | /api/v1/posts/preview | content:read | 마크다운 → HTML 미리보기 |
POST | /api/v1/images | content:write | 이미지 올리기 (바이트 또는 base64) |
GET | /api/v1/images | content:read | 올린 이미지 목록 |
GET | /api/v1/images/{id} | content:read | 이미지 한 장 |
DELETE | /api/v1/images/{id} | admin:write | 이미지 삭제 (?force=1) |
GET | /api/v1/site | admin:read | 화면 문구 조회 |
PATCH | /api/v1/site | admin:write | 화면 문구 수정 |
GET | /api/v1/menu | admin:read | 메뉴 목록 |
POST | /api/v1/menu | admin:write | 메뉴 추가 |
PATCH | /api/v1/menu/{id} | admin:write | 메뉴 수정 |
DELETE | /api/v1/menu/{id} | admin:write | 메뉴 삭제 |
GET | /api/v1/pages | content:read | 고정 페이지 목록 |
POST | /api/v1/pages | admin:write | 고정 페이지 만들기 |
PATCH | /api/v1/pages/{id} | admin:write | 고정 페이지 수정 |
DELETE | /api/v1/pages/{id} | admin:write | 고정 페이지 삭제 |
GET | /api/v1/banners | admin:read | 배너 목록 (노출·클릭) |
POST | /api/v1/banners | admin:write | 배너 만들기 |
PATCH | /api/v1/banners/{id} | admin:write | 배너 수정 |
DELETE | /api/v1/banners/{id} | admin:write | 배너 삭제 |
GET | /api/v1/review/queue | content:read | 검토 대기·재작성 목록 |
POST | /api/v1/posts/{id}/confirm | review:write | 컨펌 → 게시 |
POST | /api/v1/posts/{id}/rewrite | review:write | 재작성 지시 |
POST | /api/v1/posts/{id}/publish | review:write | 즉시 게시 |
POST | /api/v1/posts/{id}/resend | admin:write | 재발송 큐 적재 |
POST | /api/v1/review/notify | review:write | 검토 요청 메일 발송 |
GET | /api/v1/stats | admin:read | 운영 통계 |
GET | /api/v1/deliveries | admin:read | 발송 내역 (수신자 마스킹) |
POST | /api/v1/newsletter/tick | admin:write | 발송 작업 실행 |
GET | /api/v1/settings | admin:read | 설정 조회 |
PATCH | /api/v1/settings | admin:write | 설정 변경 |
12. 입력 제한과 오류
제한
| 항목 | 제한 |
|---|---|
| 제목 | 120자 |
| 요약 | 300자 (비우면 본문에서 자동 생성) |
| 본문 | 60,000자 |
| 태그 | 10개, 각 20자 |
| 슬러그 | 영문 소문자·숫자·하이픈 |
| 요청 본문 | 256KB (이미지 업로드는 6MB, base64 JSON 은 9MB) |
| 이미지 | 한 장 6MB · JPEG/PNG/GIF/WebP · 12,000px · 4천만 화소 |
| 호출 한도 | /api/v1/* 분당 600회, 검토 웹훅 분당 60회 |
오류 형식
{ "error": "이 작업에는 review:write 권한이 필요합니다. (보유: content:read, content:write)" }
| 코드 | 뜻 |
|---|---|
400 | 입력이 올바르지 않음 (주제·상태·형식) |
401 | 키 없음·잘못됨·폐기됨 |
403 | 권한(scope) 부족 |
404 | 대상 없음 (글·토큰) |
429 | 호출 한도 초과 — Retry-After 초 뒤 재시도 |
500 | 서버 오류 |
13. 자주 묻는 것
같은 글을 여러 번 올리면 중복되나요?slug 가 같으면 덮어씁니다. 다르면 새 글이 됩니다. 재작성 결과는 반드시 같은 slug 로 올리세요.
컨펌 없이 바로 게시하고 싶습니다.
설정에서 review_required 를 0 으로 두면 예전처럼 올리는 즉시 게시됩니다. PATCH /api/v1/settings 로도 바꿀 수 있습니다.
이미 게시된 글을 고치면 메일이 다시 나가나요?
아니요. 이미 게시된 글의 수정은 재발송하지 않습니다. 다시 보내려면 POST /api/v1/posts/{id}/resend 를 쓰세요.
연재 번호는 어떻게 매기나요?series_no 에 숫자를 직접 넣거나 "auto" 로 보내면 해당 주제의 다음 번호가 붙습니다. 현재 다음 번호는 GET /api/v1/categories 의 next_series_no 로 확인합니다.
키를 잃어버렸습니다.
다시 볼 수 없습니다. 관리자 화면에서 기존 키를 폐기하고 새로 발급하세요.
예약 발행은 어떻게 하나요?
글을 올릴 때 publish_at 에 미래 시각(ISO 8601)을 넣고 컨펌하면 scheduled 가 되어 그 시각에 게시·발송됩니다.
엔드포인트 전체 명세
아래는 /openapi.json 에서 자동으로 생성됩니다. 항목을 펼치면 파라미터를 볼 수 있습니다.
posts 글 작성·조회·수정
GET/api/v1/posts글 목록
| 파라미터 | 위치 | 설명 |
|---|---|---|
status | query | 상태 필터 (draft | pending | rewrite | scheduled | published | archived) |
category | query | 주제 슬러그 |
q | query | 제목·태그 검색 |
page | query | 페이지 |
limit | query | 페이지 크기(최대 100) |
locale | query | 언어 (ko·ja·en·de). 생략하면 모든 언어 |
응답:200, 401, 403, 429
POST/api/v1/posts글 올리기(같은 slug 면 덮어쓰기)
컨펌 게이트가 켜져 있으면 `pending`(검토 대기)으로 저장되고, 응답에 검토 링크·웹훅 주소가 포함됩니다. 재작성 결과를 올릴 때도 같은 `slug` 로 호출하세요.
요청 본문 — application/json
응답:200, 201, 400, 401, 403, 429
GET/api/v1/posts/{id}글 상세(본문 포함)
| 파라미터 | 위치 | 설명 |
|---|---|---|
id * | path | 글 id |
응답:200, 401, 403, 404, 429
PATCH/api/v1/posts/{id}글 수정
| 파라미터 | 위치 | 설명 |
|---|---|---|
id * | path | 글 id |
요청 본문 — application/json
응답:200, 401, 403, 404, 429
DELETE/api/v1/posts/{id}글 삭제
| 파라미터 | 위치 | 설명 |
|---|---|---|
id * | path | 글 id |
응답:200, 401, 403, 429
POST/api/v1/posts/preview마크다운 → HTML 미리보기
요청 본문 — application/json
응답:200, 401, 403, 429
review 컨펌(승인)·재작성 지시
GET/api/v1/review/queue검토 대기·재작성 목록
`status=rewrite` 로 조회하면 재작성 지시를 받은 글과 사유(`review_note`)를 가져올 수 있습니다.
| 파라미터 | 위치 | 설명 |
|---|---|---|
status | query | 기본 pending (pending | rewrite) |
body | query | 1 이면 본문 포함 (1) |
page | query | 페이지 |
limit | query | 페이지 크기 |
응답:200, 401, 403, 429
POST/api/v1/posts/{id}/confirm컨펌(승인) → 게시
예약 시각이 미래면 `scheduled` 로 두어 그 시각에 발송하고, 아니면 즉시 게시하며 구독자 발송 큐를 적재합니다. 과거에 게시된 적 있는 글은 원래 게시 시각을 유지하고 재발송하지 않습니다.
| 파라미터 | 위치 | 설명 |
|---|---|---|
id * | path | 글 id |
요청 본문 — application/json
응답:200, 401, 403, 404, 429
POST/api/v1/posts/{id}/rewrite재작성 지시
공개를 내리고 사유를 남깁니다. 에이전트는 `GET /api/v1/review/queue?status=rewrite` 로 사유를 읽어 다시 작성합니다.
| 파라미터 | 위치 | 설명 |
|---|---|---|
id * | path | 글 id |
요청 본문 — application/json
응답:200, 401, 403, 404, 429
POST/api/v1/posts/{id}/publish즉시 게시
| 파라미터 | 위치 | 설명 |
|---|---|---|
id * | path | 글 id |
응답:200, 401, 403, 429
POST/api/v1/review/notify검토 요청 메일 발송(관리자)
검토 대기 글을 묶어 관리자에게 메일로 보냅니다. 메일 안의 링크에서 바로 컨펌·재작성할 수 있습니다.
요청 본문 — application/json
응답:200, 401, 403, 429
categories 주제
GET/api/v1/categories주제 목록
| 파라미터 | 위치 | 설명 |
|---|---|---|
locale | query | 언어 (ko·ja·en·de). 생략하면 모든 언어 |
응답:200, 401, 403, 429
images 글에 쓸 이미지 업로드
GET/api/v1/images올린 이미지 목록
| 파라미터 | 위치 | 설명 |
|---|---|---|
page | query | 페이지 |
응답:200, 401, 403, 429
POST/api/v1/images이미지 올리기
두 가지 방법 중 편한 쪽을 쓰세요. 1. **바이트 그대로** — `Content-Type: image/png`(또는 image/jpeg·image/gif·image/webp) 로 파일 내용을 본문에 담아 보냅니다. 설명은 `?alt=` 쿼리로 붙입니다. 2. **JSON** — `Content-Type: application/json` 으로 `{ "data_base64": "...", "alt": "설명" }` 을 보냅니다. `data:image/png;base64,` 접두사가 붙어 있어도 됩니다. 형식은 파일 앞부분 바이트로 판별하므로 확장자·Content-Type 을 속일 수 없습니다. SVG 는 받지 않습니다. 한 장 6MB, 가로·세로 12000px, 4천만 화소까지입니다. 같은 내용을 다시 올리면 새로 저장하지 않고 기존 이미지를 그대로 돌려줍니다(`deduped: true`). 응답의 `url` 을 본문 마크다운 `` 에 넣거나, 글의 `cover_image` 로 지정하세요.
| 파라미터 | 위치 | 설명 |
|---|---|---|
alt | query | 대체 텍스트(바이트로 보낼 때) |
요청 본문 — application/json
응답:200, 201, 401, 403, 413, 415, 429
GET/api/v1/images/{hex}이미지 한 장
| 파라미터 | 위치 | 설명 |
|---|---|---|
hex * | path | 이미지 id (32자리 hex) |
응답:200, 401, 403, 404, 429
DELETE/api/v1/images/{hex}이미지 삭제
이 이미지를 쓰는 글이 있으면 409 로 막습니다. 그래도 지우려면 `?force=1` 을 붙이세요.
| 파라미터 | 위치 | 설명 |
|---|---|---|
hex * | path | 이미지 id (32자리 hex) |
force | query | 쓰는 글이 있어도 삭제 (1) |
응답:200, 401, 403, 404, 409, 429
site 화면 문구 · 메뉴 · 고정 페이지 · 배너(광고)
GET/api/v1/site화면 문구 조회
각 문구의 현재 값과 기본값·최대 길이·형식을 함께 돌려줍니다. `kind` 가 `rich` 면 `**굵게**` 와 `[링크](/주소)` 를, `lines` 면 줄바꿈을 쓸 수 있습니다.
| 파라미터 | 위치 | 설명 |
|---|---|---|
locale | query | 언어 (ko·ja·en·de). 생략하면 모든 언어 |
응답:200, 401, 403, 429
PATCH/api/v1/site화면 문구 수정
보낸 키만 바뀝니다. 빈 문자열로 보내면 기본 문구로 되돌아갑니다. 어떤 값도 HTML 로 해석되지 않습니다.
| 파라미터 | 위치 | 설명 |
|---|---|---|
locale | query | 언어 (ko·ja·en·de). 생략하면 모든 언어 |
요청 본문 — application/json
응답:200, 400, 401, 403, 429
GET/api/v1/menu메뉴 목록
| 파라미터 | 위치 | 설명 |
|---|---|---|
locale | query | 언어 (ko·ja·en·de). 생략하면 모든 언어 |
응답:200, 401, 403, 429
POST/api/v1/menu메뉴 추가
요청 본문 — application/json
응답:201, 400, 401, 403, 429
PATCH/api/v1/menu/{id}메뉴 수정
| 파라미터 | 위치 | 설명 |
|---|---|---|
id * | path | 메뉴 id |
요청 본문 — application/json
응답:200, 401, 403, 404, 429
DELETE/api/v1/menu/{id}메뉴 삭제
| 파라미터 | 위치 | 설명 |
|---|---|---|
id * | path | 메뉴 id |
응답:200, 401, 403, 404, 429
GET/api/v1/pages고정 페이지 목록
| 파라미터 | 위치 | 설명 |
|---|---|---|
locale | query | 언어 (ko·ja·en·de). 생략하면 모든 언어 |
응답:200, 401, 403, 429
POST/api/v1/pages고정 페이지 만들기
`about` · `privacy` · `terms` 로 만들면 기본 화면을 대신합니다. 그 밖의 주소는 `/page/{slug}` 로 열립니다.
요청 본문 — application/json
응답:201, 401, 403, 409, 429
PATCH/api/v1/pages/{id}고정 페이지 수정
| 파라미터 | 위치 | 설명 |
|---|---|---|
id * | path | 페이지 id |
요청 본문 — application/json
응답:200, 401, 403, 404, 429
DELETE/api/v1/pages/{id}고정 페이지 삭제
기본 화면이 있는 주소라면 지운 뒤 기본 화면이 다시 나갑니다.
| 파라미터 | 위치 | 설명 |
|---|---|---|
id * | path | 페이지 id |
응답:200, 401, 403, 404, 429
GET/api/v1/banners배너 목록(노출·클릭 포함)
| 파라미터 | 위치 | 설명 |
|---|---|---|
locale | query | 언어 (ko·ja·en·de). 생략하면 모든 언어 |
응답:200, 401, 403, 429
POST/api/v1/banners배너 만들기
요청 본문 — application/json
응답:201, 400, 401, 403, 429
PATCH/api/v1/banners/{id}배너 수정
| 파라미터 | 위치 | 설명 |
|---|---|---|
id * | path | 배너 id |
요청 본문 — application/json
응답:200, 401, 403, 404, 429
DELETE/api/v1/banners/{id}배너 삭제
| 파라미터 | 위치 | 설명 |
|---|---|---|
id * | path | 배너 id |
응답:200, 401, 403, 404, 429
ops 운영 정보·발송·설정
POST/api/v1/posts/{id}/resend이미 게시된 글 재발송 큐 적재
| 파라미터 | 위치 | 설명 |
|---|---|---|
id * | path | 글 id |
응답:200, 401, 403, 429
GET/api/v1/stats운영 통계
응답:200, 401, 403, 429
GET/api/v1/deliveries발송 내역(수신자 마스킹)
| 파라미터 | 위치 | 설명 |
|---|---|---|
status | query | 상태 |
page | query | 페이지 |
limit | query | 페이지 크기 |
응답:200, 401, 403, 429
POST/api/v1/newsletter/tick발송 작업 실행(예약 게시·다이제스트·큐 처리)
응답:200, 401, 403, 429
GET/api/v1/settings설정 조회
응답:200, 401, 403, 429
PATCH/api/v1/settings설정 변경
요청 본문 — application/json
응답:200, 401, 403, 429
webhook 토큰 기반 검토 웹훅 (API 키 불필요)
POST/api/review/{token}/confirm검토 토큰으로 컨펌
인증 불필요 — 주소에 포함된 토큰이 인증 수단입니다.
| 파라미터 | 위치 | 설명 |
|---|---|---|
token * | path | 글별 검토 토큰 |
요청 본문 — application/json
응답:200, 404
POST/api/review/{token}/rewrite검토 토큰으로 재작성 지시
인증 불필요 — 주소에 포함된 토큰이 인증 수단입니다.
| 파라미터 | 위치 | 설명 |
|---|---|---|
token * | path | 글별 검토 토큰 |
요청 본문 — application/json
응답:200, 404
GET/api/review/{token}검토 대상 상태 조회
인증 불필요 — 주소에 포함된 토큰이 인증 수단입니다.
| 파라미터 | 위치 | 설명 |
|---|---|---|
token * | path | 글별 검토 토큰 |
응답:200, 404