콘텐츠 API
콘텐츠 API는 서버용 비공개 키가 필요합니다.
Authorization: Bearer {SERVER_PRIVATE_API_KEY}Accept: application/json콘텐츠 목록 조회
섹션 제목: “콘텐츠 목록 조회”GET /api/public/v1/contentsQuery parameters
섹션 제목: “Query parameters”| 이름 | 필수 | 타입 | 설명 |
|---|---|---|---|
language | 선택 | string | ko, en, ja. 기본값 ko |
page | 선택 | integer | 페이지 번호. 기본값 1 |
limit | 선택 | integer | 페이지당 개수. 기본값 20, 최대 100 |
status | 선택 | string | publish, pending, paused, in_review, banned |
content_type | 선택 | string | vod, live, offline, digital_content |
category_id | 선택 | integer | 특정 카테고리의 콘텐츠만 조회 |
category_ids | 선택 | array 또는 comma string | 여러 카테고리 중 하나에 속한 콘텐츠 조회 |
tag_id | 선택 | integer | 특정 태그의 콘텐츠만 조회 |
tag_ids | 선택 | array 또는 comma string | 여러 태그 중 하나가 붙은 콘텐츠 조회 |
search | 선택 | string | 콘텐츠명 검색 |
curl -X GET "https://{site_host}/api/public/v1/contents?content_type=vod&page=1" \ -H "Authorization: Bearer {SERVER_PRIVATE_API_KEY}" \ -H "Accept: application/json"콘텐츠 상세 조회
섹션 제목: “콘텐츠 상세 조회”GET /api/public/v1/contents/{contentId}Query parameters
섹션 제목: “Query parameters”| 이름 | 필수 | 타입 | 설명 |
|---|---|---|---|
language | 선택 | string | ko, en, ja. 기본값 ko |
콘텐츠 생성
섹션 제목: “콘텐츠 생성”POST /api/public/v1/contentsapplication/json 또는 multipart/form-data로 전송합니다.
공통 필드
섹션 제목: “공통 필드”| 이름 | 필수 | 타입 | 설명 |
|---|---|---|---|
content_type | 필수 | string | vod, live, offline, digital_content |
title | 필수 | string | 콘텐츠명 |
description_html | 필수 | string | 콘텐츠 상세 HTML |
category_ids | 필수 | array of integer | 카테고리 ID 목록 |
featured_image | 필수 | file 또는 string | 대표 이미지 |
status | 선택 | string | 기본값 pending |
images | 선택 | array | 추가 이미지 목록 |
thumbnail_link | 선택 | string | 썸네일 링크 |
inventory_display_mode | 선택 | string | 재고 표시 방식 |
installment_month | 선택 | integer | 0부터 36 |
allow_link | 선택 | string | 외부 허용 링크 |
tag_ids | 선택 | array | 콘텐츠 태그 ID 목록. 현재 콘텐츠당 1개까지 선택 가능 |
options | 필수 | array | 콘텐츠 유형에 맞는 구매 옵션 목록 |
유형별 options 필드
섹션 제목: “유형별 options 필드”| 유형 | 필수 필드 | 설명 |
|---|---|---|
vod | title, base_price, download_limit_days | 옵션은 정확히 1개이며 chapters도 1개 이상 필요합니다. |
live | title, base_price, start_at, end_at, access_url | access_url은 수강생에게 제공할 접속 링크입니다. |
offline | title, base_price, start_at, end_at, location_text | location_detail, capacity를 추가할 수 있습니다. |
digital_content | title, base_price, download_limit_days, files | files에는 HTTPS URL, 기존 file_id, multipart 파일을 넣습니다. |
VOD의 chapters에는 title과 items가 필요합니다. 각 item은 title, media_url, duration_text를 가집니다.
최소 콘텐츠 생성 payload
섹션 제목: “최소 콘텐츠 생성 payload”콘텐츠는 타입별로 payload shape가 달라집니다. 아래 예시 중 콘텐츠 유형에 맞는 payload를 사용합니다.
VOD
섹션 제목: “VOD”{ "content_type": "vod", "title": "포토샵 입문 클래스", "description_html": "<p>처음 시작하는 분들을 위한 강의입니다.</p>", "status": "publish", "category_ids": [11], "tag_ids": [23], "featured_image": "https://example.com/main.jpg", "options": [ { "title": "수강권", "base_price": 39000, "sale_price": 29000, "is_on_sale": true, "download_limit_days": 90 } ], "chapters": [ { "title": "1. 시작하기", "items": [ { "title": "강의 소개", "media_url": "https://video.example.com/intro.mp4", "duration_text": "08:30" } ] } ]}Offline
섹션 제목: “Offline”{ "content_type": "offline", "title": "브랜드 포지셔닝 워크숍", "description_html": "<p>오프라인 워크숍입니다.</p>", "status": "publish", "category_ids": [11], "featured_image": "https://example.com/offline.jpg", "options": [ { "title": "7월 12일 토요일 14:00", "base_price": 99000, "sale_price": 99000, "start_at": "2026-07-12 14:00:00", "end_at": "2026-07-12 18:00:00", "location_text": "서울 성수동" } ]}Live
섹션 제목: “Live”{ "content_type": "live", "title": "라이브 광고 런칭 세션", "description_html": "<p>실시간 라이브 세션입니다.</p>", "status": "publish", "category_ids": [11], "featured_image": "https://example.com/live.jpg", "options": [ { "title": "8월 3일 일요일 20:00", "base_price": 59000, "sale_price": 59000, "start_at": "2026-08-03 20:00:00", "end_at": "2026-08-03 22:00:00", "access_url": "https://meet.example.com/live-session" } ]}Digital content
섹션 제목: “Digital content”{ "content_type": "digital_content", "title": "SEO 토픽 클러스터 가이드", "description_html": "<p>다운로드형 디지털 자료입니다.</p>", "status": "publish", "category_ids": [11], "featured_image": "https://example.com/digital.jpg", "options": [ { "title": "파일 다운로드", "base_price": 29000, "sale_price": 29000, "download_limit_days": 90, "files": [ "https://files.example.com/seo-topic-cluster-guide.pdf" ] } ]}핵심 규칙:
content_type은 생성 후 변경하지 않습니다.category_ids는 배열입니다.- 처음 연동할 때는
featured_image에 HTTPS 이미지 URL을 보내는 방식이 가장 단순합니다. 파일 업로드가 필요하면multipart/form-data예시를 사용합니다. - 모든 콘텐츠 유형은
options를 최소 1개 이상 보냅니다. VOD는 옵션이 정확히 1개여야 합니다. offline,live,digital_content는 구매 옵션이 사실상 핵심 데이터입니다.
curl -X POST "https://{site_host}/api/public/v1/contents" \ -H "Authorization: Bearer {SERVER_PRIVATE_API_KEY}" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "content_type": "vod", "title": "포토샵 입문 클래스", "description_html": "<p>처음 시작하는 분들을 위한 강의입니다.</p>", "status": "publish", "category_ids": [11], "tag_ids": [23], "featured_image": "https://example.com/main.jpg", "options": [{ "title": "수강권", "base_price": 39000, "download_limit_days": 90 }], "chapters": [{ "title": "1. 시작하기", "items": [{ "title": "강의 소개", "media_url": "https://video.example.com/intro.mp4", "duration_text": "08:30" }] }] }'multipart/form-data 기준
섹션 제목: “multipart/form-data 기준”대표 이미지를 파일로 올릴 때는 아래처럼 시작할 수 있습니다.
curl -X POST "https://{site_host}/api/public/v1/contents" \ -H "Authorization: Bearer {SERVER_PRIVATE_API_KEY}" \ -H "Accept: application/json" \ -F "content_type=vod" \ -F "title=포토샵 입문 클래스" \ -F "description_html=<p>처음 시작하는 분들을 위한 강의입니다.</p>" \ -F "category_ids[]=11" \ -F "featured_image=@/absolute/path/main.jpg"콘텐츠 수정
섹션 제목: “콘텐츠 수정”PUT /api/public/v1/contents/{contentId}전달한 필드만 업데이트합니다. 전달하지 않은 필드는 기존 값을 유지합니다.
주의 사항
섹션 제목: “주의 사항”content_type변경은 지원하지 않습니다.- 수정 요청은 대상 콘텐츠의 현재 유형을 기준으로 검증합니다.
- 오프라인 콘텐츠에 VOD 전용
chapters구조를 보내면422오류가 반환될 수 있습니다. featured_image를 보내면 대표 이미지가 교체됩니다.images를 보내면 추가 이미지 전체가 교체됩니다.category_ids를 보내면 카테고리 전체가 교체됩니다.tag_ids를 보내면 콘텐츠 태그가 교체됩니다. 빈 배열을 보내면 태그가 제거됩니다.options를 보내면 해당 타입의 옵션 구조가 동기화됩니다.- VOD에서
chapters를 보내면 챕터/아이템 구조가 동기화됩니다.
curl -X PUT "https://{site_host}/api/public/v1/contents/4321" \ -H "Authorization: Bearer {SERVER_PRIVATE_API_KEY}" \ -H "Content-Type: application/json" \ -H "Accept: application/json" \ -d '{ "title": "포토샵 입문 클래스 v2", "status": "publish", "category_ids": [11, 12], "tag_ids": [23] }'최소 콘텐츠 수정 payload
섹션 제목: “최소 콘텐츠 수정 payload”{ "title": "포토샵 입문 클래스 v2", "status": "publish", "category_ids": [11, 12], "tag_ids": [23]}수정 규칙:
content_type변경은 하지 않습니다.category_ids,tag_ids,images,options,chapters는 보내면 전체 교체로 처리됩니다.offline/live에 VOD용chapters를 보내지 않습니다.
대표 응답 필드
섹션 제목: “대표 응답 필드”| 필드 | 설명 |
|---|---|
content_id | 콘텐츠 ID |
title | 콘텐츠명 |
description_html | 콘텐츠 상세 HTML |
content_type | 콘텐츠 유형 |
status | 콘텐츠 상태 |
language | 언어 코드 |
category_ids | 카테고리 ID 목록 |
categories | 카테고리 상세 목록 |
tag_ids | 연결된 콘텐츠 태그 ID |
featured_image | 대표 이미지 |
images | 추가 이미지 |
base_price | 기본가 |
sale_price | 할인가 |
is_on_sale | 할인 여부 |
is_free | 무료 여부 |
options | 구매 옵션 |
chapters | VOD 챕터 구조 |
created_at | 생성 일시 |
updated_at | 수정 일시 |
End-to-end 예시: 콘텐츠 서버 연동
섹션 제목: “End-to-end 예시: 콘텐츠 서버 연동”GET /api/public/v1/content-categories/search?query=포토샵으로 카테고리 ID를 찾습니다.- 자체 CMS 데이터의
content_type을 먼저 확정합니다. - 타입에 맞는 canonical payload를 만듭니다.
- 새 콘텐츠면
POST /api/public/v1/contents - 기존 콘텐츠면
PUT /api/public/v1/contents/{contentId} - 생성/수정 후 자체 시스템에 Runmoa content id를 매핑 저장합니다.
오류 처리 cookbook
섹션 제목: “오류 처리 cookbook”401 Unauthorized
섹션 제목: “401 Unauthorized”- private API key 누락 또는 오타
403 Forbidden
섹션 제목: “403 Forbidden”- 다른 사이트용 key 사용
- 권한 없는 환경에서 호출
422 Unprocessable Entity
섹션 제목: “422 Unprocessable Entity”content_type과 payload 구조가 맞지 않음- 잘못된
category_ids - 이미지 형식 오류
offline/live일정 필드 누락vod챕터 구조 불일치
422 응답을 받으면 content_type에 맞게 payload shape를 수정한 뒤 다시 호출합니다.