블로그 팩토리 · 게시판 연동 · v1
글쓰기 API 문서
게시판에 올라온 글을 글감으로 보내면, 블로그 팩토리 작업자가 검색 노출용 정보성 블로그 글을 쓰고 완성된 글을 HTML 로 돌려주는 API 입니다. 관리자가 발급한 글쓰기 키로만 호출할 수 있습니다. 글은 등록 즉시 만들어지지 않고, 작업자가 가져가 쓴 뒤 상태가 ready 가 되면 받아 갑니다.
0. 한눈에 보는 흐름
- 게시판 서버가
POST /api/v1/writing/jobs로 글감(제목·본문·작성자·사이트)을 등록합니다 →queued. - 작업자가 가져가 글을 쓰고 제출합니다 →
ready(검수가 필요한 작업은review→ 관리자 승인 →ready). callback_url로 알림을 받거나GET /jobs/{id}를 폴링해ready를 확인합니다.GET /jobs/{id}/html로 완성 글 HTML 을 받아 자기 블로그에 올립니다. 이미지는 자리(alt)만 내려오므로 게시판 쪽에서 채웁니다.POST /jobs/{id}/published로 게시 주소를 보고합니다 →published.
완성 글의 구조는 고정입니다: h1 제목 · 검색 설명문 · 리드 문단 · 소제목(h2) 3~8개(각 이미지 자리 + 문단) · FAQ(FAQPage JSON-LD) · CTA · 관련 글. 문단 강조는 <b class="pt">(포인트)와 <b class="warn">(주의) 두 클래스만 씁니다.
1. 인증
모든 요청에 발급받은 키를 헤더로 보냅니다. x-api-key 가 기본이고 Authorization: Bearer <키> 도 같은 뜻으로 받습니다. 글쓰기 전용 키는 bfwrt_ 로 시작하는 54자 문자열이며 서버에는 해시만 저장되므로, 발급 화면에서 한 번만 표시됩니다. 잃어버리면 관리자에게 폐기·재발급을 요청하세요.
writing:read— 작업 목록·상세·HTML 조회. 항상 포함됩니다.writing:write— 글감 등록, 작업 가져가기, 결과 제출, 게시·실패 보고. 게시판 서버와 작업자 모두 필요합니다.- 글쓰기 키로 VOC API 를 부르면
403입니다. 키는 서버 간 통신에만 쓰고 브라우저·앱·공개 저장소·로그에 두지 마세요.
curl -H "x-api-key: $WRITING_KEY" https://blogfactory.kr/api/v1/writing/me
# {"key":{"id":"…","name":"메가헤르츠 게시판","scopes":["writing:read","writing:write"]},
# "server_time":"2026-09-17T10:20:00.000Z","statuses":[…],"endpoints":[…]}2. 작업 상태
| status | 표시 | 뜻 | 다음 |
|---|---|---|---|
| queued | 대기 | 등록됨. 작업자가 가져가기 전 | writing · cancelled(관리자) |
| writing | 작성 중 | 작업자가 글을 쓰는 중 | ready / review · failed / queued |
| review | 검수 대기 | review_required 작업. 관리자 승인 전까지 HTML 이 열리지 않음 | ready(승인) · queued(반려) |
| ready | 완료 | 완성. GET /html 가능 — 올린 뒤 published 로 보고 | published · queued(다시 쓰기) |
| published | 게시됨 | 호출자가 게시 주소를 보고함 | — |
| failed | 실패 | 작업자가 쓰지 못함(error 에 사유) | queued(관리자 재대기) |
| cancelled | 취소 | 관리자 취소 | queued(관리자 재대기) |
응답의 status_label 은 한글 표시입니다. 상태 전이가 안 되는 요청은 409 와 현재 status 를 돌려줍니다.
3. 엔드포인트 — 게시판(호출자)
모든 응답은 JSON(UTF-8)이며 시각은 ISO 8601 UTC 입니다. HTML 엔드포인트만 text/html 입니다.
/api/v1/writing/jobs글감 등록 · 권한 writing:write| 본문 필드 | 설명 | 기본 |
|---|---|---|
| site * | 대상 사이트 식별자. 소문자·숫자·하이픈 1~40자(예 megahertz) | — |
| title * | 게시글 제목(200자) | — |
| body * | 게시글 본문(20,000자, 평문 또는 마크다운) | — |
| author_name | 게시글 작성자 표시 이름(60자) | "" |
| category | 게시판 분류(40자) | "" |
| keywords | 노출 목표 키워드 배열(10개, 각 40자) | [] |
| memo | 작업자에게 남기는 지시 — 톤·분량·꼭 넣을 내용(2,000자) | "" |
| source_ref / source_url | 호출자 쪽 게시글 id(120자) / 원 게시글 주소(http·https) | "" / null |
| source_type | api 또는 board | api |
| review_required | true 면 완성 뒤 review 에 머물고 관리자 승인 뒤 ready | false |
| callback_url | 완성(ready) 시 POST 할 https 주소 | null |
curl -X POST -H "x-api-key: $WRITING_KEY" -H "Content-Type: application/json" \
https://blogfactory.kr/api/v1/writing/jobs \
-d '{
"site": "megahertz",
"source_type": "board", "source_ref": "42", "source_url": "https://megahertz.co.kr/board/42",
"title": "병원 블로그, 어떻게 시작해야 하나요",
"body": "개원 3개월 차인데 블로그를 시작하려고 합니다. 무엇부터 정해야 할지 …",
"author_name": "홍길동", "category": "병원",
"keywords": ["병원마케팅", "병원 블로그"],
"memo": "의료광고법 주의 문장 꼭 포함",
"callback_url": "https://megahertz.co.kr/hooks/writing"
}'
# 201 {"job":{"id":"8f4c2a1e-…","status":"queued","status_label":"대기","site":"megahertz", …}}/api/v1/writing/jobs/{id}작업 상세 · 결과(article 구조 + html) 포함{
"job": {
"id": "8f4c2a1e-5b7d-4c3e-9a1f-2d6b8e0c4a71",
"status": "ready", "status_label": "완료", "site": "megahertz",
"source": { "type": "board", "ref": "42", "url": "https://megahertz.co.kr/board/42" },
"author_name": "홍길동", "title": "병원 블로그, 어떻게 시작해야 하나요",
"category": "병원", "keywords": ["병원마케팅", "병원 블로그"], "review_required": false,
"claimed_by": "작업자 맥", "claimed_at": "2026-09-17T10:05:00.000Z", "attempts": 1,
"has_result": true, "result_title": "병원마케팅, 검색 상위노출부터 문의까지", "result_body_chars": 1980,
"published_url": null, "published_at": null, "error": null,
"created_at": "2026-09-17T10:00:00.000Z", "updated_at": "2026-09-17T10:12:00.000Z",
"body": "개원 3개월 차인데 …", "memo": "의료광고법 주의 문장 꼭 포함",
"callback_url": "https://megahertz.co.kr/hooks/writing",
"result": {
"article": {
"title": "병원마케팅, 검색 상위노출부터 문의까지",
"description": "병원마케팅의 검색 상위노출 원리와 …",
"lead": "병원마케팅은 광고비를 늘리는 일이 아니라 …",
"sections": [
{ "heading": "병원마케팅, 무엇부터 정해야 할까",
"paragraphs": ["가장 먼저 정할 것은 …", "**검색어는 병원 용어가 아니라 환자의 언어로** 정합니다."],
"image": { "alt": "병원마케팅, 무엇부터 정해야 할까", "src": null, "caption": null } }
],
"faq": [{ "question": "병원마케팅 효과는 얼마 만에 나타나나요?", "answer": "콘텐츠 축적형이라 …" }],
"thumbnail": { "alt": "병원마케팅, 검색 상위노출부터 문의까지", "src": null, "caption": null },
"tags": ["병원마케팅", "상위노출"], "author": "메가헤르츠 마케팅연구소",
"cta": { "heading": "우리 가게는 지금 검색 몇 위일까요?", "body": "…", "label": "무료 진단 신청하기", "href": "/#apply" },
"related": []
},
"html": "<h1>병원마케팅, 검색 상위노출부터 문의까지</h1>\n<div class=\"meta\">…</div>…",
"html_source": "server", "worker": "작업자 맥", "notes": "", "body_chars": 1980,
"submitted_at": "2026-09-17T10:12:00.000Z"
}
}
}result는 제출 뒤부터 보이지만(검수 대기 중 미리보기 가능) 게시는status가ready일 때만 하세요.result.html은 아래 HTML 엔드포인트의 본문 조각과 같습니다.html_source가worker면 작업자가 직접 만든 HTML 입니다.
/api/v1/writing/jobs/{id}/html완성 글 HTML · text/html · ready·published 에서만 200| 파라미터 | 설명 |
|---|---|
| (없음) | 본문 조각 — <h1>·메타 줄·리드·<h2> 섹션·이미지 자리·FAQ <dl>·CTA·관련 글. 호출자 페이지 틀 안에 넣습니다. 작업자가 직접 HTML 을 준 경우 그것(헤더 x-writing-html-source: worker) |
| format=document | 독립 HTML 한 장 — <head> 메타(title·description·og)·canonical·FAQPage/Article JSON-LD·최소 CSS 포함. 정적 페이지로 그대로 올릴 때 |
| site_name · canonical_url · published_at · logo_url | format=document 의 메타와 JSON-LD 를 채웁니다 |
# 본문 조각(호출자 페이지 틀 안에 넣기)
curl -H "x-api-key: $WRITING_KEY" "https://blogfactory.kr/api/v1/writing/jobs/<id>/html" > article.html
# 독립 HTML 한 장(정적 페이지로 그대로 올리기)
curl -G -H "x-api-key: $WRITING_KEY" "https://blogfactory.kr/api/v1/writing/jobs/<id>/html" \
--data-urlencode "format=document" \
--data-urlencode "site_name=메가헤르츠" \
--data-urlencode "canonical_url=https://megahertz.co.kr/blog/hospital-marketing.html" > page.html본문 조각의 모양(이미지는 자리와 alt 만 옵니다 — <!-- image-slot:… --> 를 <img> 나 <figure> 로 바꿔 넣으세요. CTA 링크와 마지막 줄의 1×1 비콘은 성과 측정용이니 그대로 두세요):
<h1>병원마케팅, 검색 상위노출부터 문의까지</h1>
<div class="meta">메가헤르츠 마케팅연구소 · 2026년 09월 17일</div>
<!-- image-slot:thumb alt="병원마케팅, 검색 상위노출부터 문의까지" -->
<p class="lead">병원마케팅은 광고비를 늘리는 일이 아니라 …</p>
<h2>병원마케팅, 무엇부터 정해야 할까</h2>
<!-- image-slot:s0 alt="병원마케팅, 무엇부터 정해야 할까" -->
<p>가장 먼저 정할 것은 목표 환자와 핵심 진료입니다.</p>
<p><b class="pt">검색어는 병원 용어가 아니라 환자의 언어로</b> 정합니다.</p>
…
<div class="faq"><h2>자주 묻는 질문</h2><dl><dt>Q. 병원마케팅 효과는 얼마 만에 나타나나요?</dt><dd>…</dd></dl></div>
<div class="ctabox"><b>우리 가게는 지금 검색 몇 위일까요?</b><p>…</p>
<a href="https://blogfactory.kr/go/8f4c2a1e-…" target="_blank" rel="noopener" data-cta="tracked">카카오톡으로 무료 진단 신청하기</a></div>
<img src="https://blogfactory.kr/go/8f4c2a1e-…/view.gif" alt="" width="1" height="1" aria-hidden="true" data-beacon="view">/api/v1/writing/jobs작업 목록 · 요약만(본문·결과 없음)| 파라미터 | 설명 | 기본 |
|---|---|---|
| status | 상태. 쉼표로 여러 개(예 ready,published) | 전체 |
| site | 사이트 식별자 | 전체 |
| since | created_at >= since. ISO 8601 | 없음 |
| order | asc · desc (created_at 기준) | asc |
| page / page_size | 1부터 시작, 페이지 크기 1~100 | 1 / 50 |
응답 { jobs, page, page_size, total, total_pages, next_page }. 콜백을 놓쳤을 때는 site=<사이트>&status=ready 로 주기적으로 찾으면 됩니다.
/api/v1/writing/jobs/{id}/published게시 보고 · 권한 writing:write · ready → publishedcurl -X POST -H "x-api-key: $WRITING_KEY" -H "Content-Type: application/json" \
"https://blogfactory.kr/api/v1/writing/jobs/<id>/published" \
-d '{"url":"https://megahertz.co.kr/blog/hospital-marketing.html"}'
# 200 {"job":{"status":"published", "published_url":"https://…", …}}4. 엔드포인트 — 작업자
글을 쓰는 쪽(블로그 팩토리 작업자 세션)이 씁니다. 게시판 연동만 하면 이 절은 참고만 하세요.
/api/v1/writing/jobs/{id}/claim{ worker? } · queued → writing · 동시 호출 시 한쪽만 200/api/v1/writing/jobs/{id}/result{ article, html?, worker?, notes? } · writing|queued → ready(검수 필요 시 review)/api/v1/writing/jobs/{id}/fail{ error, requeue? } · writing → failed(requeue 면 5회까지 queued)article 구조
| 필드 | 규칙 |
|---|---|
| title | 5~120자. h1 |
| description | 20~320자. 검색 결과 설명문(meta description·og) |
| lead | 20~1,200자. 첫 문단(결론부터) |
| sections[] | 3~8개. { heading(≤80), paragraphs[](1~8개, 각 ≤1,500), image?{ alt(필수 ≤200), src?, caption? } } |
| faq[] | 0~8개. { question(≤200), answer(≤1,000) }. "Q." 접두는 붙이지 않습니다 |
| thumbnail? | { alt, src?, caption? } |
| tags? · author? | ≤10개(JSON-LD keywords) · 메타 줄과 Article author(60자) |
| cta? · related?[] | { heading, body, label, href } · ≤8개 { title, href }. href 는 http(s) 또는 / 경로 |
문단은 평문이며 강조 표기 두 가지만 씁니다: **핵심 문장** → <b class="pt">, !!주의 문장!! → <b class="warn">. 태그를 넣어도 문자로 이스케이프됩니다. 서버가 이 구조로 HTML 과 JSON-LD 를 만들고, html 을 함께 주면 호출자에게는 그것을 내려줍니다.
# 1) 대기 작업 → 가져가기
curl -H "x-api-key: $WORKER_KEY" "https://blogfactory.kr/api/v1/writing/jobs?status=queued&order=asc"
curl -X POST -H "x-api-key: $WORKER_KEY" -H "Content-Type: application/json" \
"https://blogfactory.kr/api/v1/writing/jobs/<id>/claim" -d '{"worker":"작업자 맥"}'
# 2) 글 제출(서버가 HTML 렌더링; html 을 함께 주면 그것을 호출자에게 내려줌)
curl -X PUT -H "x-api-key: $WORKER_KEY" -H "Content-Type: application/json" \
"https://blogfactory.kr/api/v1/writing/jobs/<id>/result" \
-d '{"article": { "title": "…", "description": "…", "lead": "…", "sections": [ … ], "faq": [ … ] }, "worker": "작업자 맥"}'
# 200 {"job":{"status":"ready", …}, "callback_sent": true}
# 3) 못 쓴 경우
curl -X POST -H "x-api-key: $WORKER_KEY" -H "Content-Type: application/json" \
"https://blogfactory.kr/api/v1/writing/jobs/<id>/fail" -d '{"error":"본문이 비어 있음","requeue":false}'5. 콜백
등록 시 callback_url(https)을 주면 글이 완성될 때(ready, 검수 작업은 승인 시) 아래 JSON 을 POST 합니다. HTML 은 크기 때문에 넣지 않으니 html_url 을 다시 부르세요. 5초 안에 응답이 없거나 실패해도 작업 상태는 그대로이며 관리자 이력에 남습니다.
POST <callback_url> (헤더 x-writing-event: ready, 5초 제한)
{
"event": "ready",
"job": { "id": "8f4c2a1e-…", "status": "ready", "site": "megahertz", "title": "…", "result_title": "…", "result_body_chars": 1980, … },
"detail_url": "https://blogfactory.kr/api/v1/writing/jobs/8f4c2a1e-…",
"html_url": "https://blogfactory.kr/api/v1/writing/jobs/8f4c2a1e-…/html",
"sent_at": "2026-09-17T10:12:01.000Z"
}5-1. 성과 측정 — 게시글별 조회·CTA 클릭
HTML 을 내려줄 때 서버가 두 가지를 심습니다. 글이 어느 사이트에 올라가도 블로그 팩토리 쪽에서 게시글별 성과가 잡힙니다.
- CTA 경유 링크
https://blogfactory.kr/go/{id}— 독자가 누르면 클릭 1건을 기록하고 글의 CTA 목적지(예: 카카오톡 오픈채팅)로 바로 넘깁니다. 목적지는 서버에만 있어 주소를 바꿔 다른 곳으로 보낼 수 없습니다. - 조회 비콘
https://blogfactory.kr/go/{id}/view.gif— 글 끝의 1×1 이미지 요청 한 번이 조회 1건입니다. 자바스크립트가 필요 없습니다. - 봇과 같은 방문자의 분당 30회 초과는 세지 않습니다. IP·브라우저 원문은 저장하지 않고 하루 단위 방문자 해시와 리퍼러 호스트만 남깁니다.
- 작업 응답의
view_count·click_count·click_rate(%) 로 연동 쪽에서도 볼 수 있습니다. 관리자 화면은 고유 방문자와 최근 14일 일별 표까지 보여 줍니다. - CTA 문구는 바꿔도 되지만
href와 비콘은 그대로 두어야 성과가 잡힙니다.
5-2. 블로그 팩토리 칼럼(site=blogfactory) 자동 게시
site가blogfactory인 작업은 완료되는 순간(검수 작업은 승인 때) 서버가published로 바꿔 blogfactory.kr/blog 의/blog/{slug}.html에 공개합니다. 응답에slug·column_path가 붙습니다. 다른 사이트 작업은 종전처럼 호출자가 HTML 을 받아 올립니다.- 제출 조건 —
article.slug(영문 소문자·숫자·하이픈)와article.summary(핵심 요약 2~5줄)가 없으면 400 입니다. 요약은 본문의 ‘핵심 요약’ 상자와 JSON-LD abstract·speakable 로 나가 검색·생성형 AI 답변에 쓰입니다. - 검색 노출 — 본문 페이지는 canonical·robots·OpenGraph(article)·Article/FAQPage/BreadcrumbList JSON-LD 를 내보내고,
/sitemap.xml·/robots.txt·/rss.xml이 있습니다. - 칼럼 주제(
category)는 블로그 글쓰기 · 온라인 수익화 · 브랜딩 셋입니다. - 개인정보 가리기 — 등록 시 제목·본문의 이메일, 휴대폰·유선 번호, 주민·카드·계좌번호, 개인 SNS 주소, 카톡 아이디를
[이메일]같은 표지로 바꿔 저장하고,source_type: board면 작성자 이름을 첫 글자만 남깁니다(응답masked_personal_info). 이름·상호처럼 형태로 못 잡는 것은 작업자가 글에서 일반화합니다.
6. 오류
| 코드 | 뜻 | 조치 |
|---|---|---|
| 400 | 파라미터·본문 오류. 본문 error 에 이유가 있습니다 | 요청을 수정합니다 |
| 401 | 키 없음 · 형식 오류 · 폐기 · 만료 | 관리자에게 키 확인 또는 재발급을 요청합니다 |
| 403 | 권한 범위 밖(읽기 키로 등록·제출, 글쓰기 키로 VOC 호출) | 관리자에게 writing:write 부여를 요청합니다 |
| 404 | 없는 작업 id | — |
| 409 | 상태 전이 불가 — 다른 작업자가 먼저 가져감, 검수 대기 중 HTML 요청, 이미 게시됨 등. 본문 status 에 현재 상태 | 상세를 다시 읽고 상태에 맞게 진행합니다 |
| 429 | 분당 120회 초과 | 잠시 뒤 재시도(지수 백오프) |
| 500 | 서버 오류 | 잠시 뒤 재시도. 반복되면 시각과 요청 URL 을 관리자에게 전달합니다 |
오류 응답은 항상 {"error": "사람이 읽을 수 있는 이유"} 형태이고, 상태 충돌(409)에는 status 가 함께 옵니다.
7. 운영 정보
- 키 발급·폐기·만료·권한 설정과 작업 검수(승인·반려·취소·재대기)는 블로그 팩토리 관리자가 합니다. 키마다 발급자와 마지막 사용 시각이, 작업마다 이벤트 이력이 기록됩니다.
- 글은 작업자가 켜져 있을 때 처리되므로 등록부터 완성까지 시간이 걸립니다. 게시판 쪽은 콜백 또는 폴링으로 기다리는 구조로 만들어 주세요.
- 사실 확인이 필요한 주장·법률 문구는 작업자가 완곡하게 씁니다. 사이트 고유의 CTA·관련 글 목록은
memo로 알려 주면 반영됩니다. - 호출 제한은 키 단위 분당 120회입니다. 한글 파라미터는 반드시 URL 인코딩해 보냅니다.
- 키 발급이나 연동 문의는 고객센터로 보내 주세요.
문서 버전 2026-09-17 · API 버전 v1