기업용 콘텐츠 검수 API
기업 대시보드에서 사용하는 콘텐츠 검수 관련 API
기업용 콘텐츠 검수 API
기업 대시보드에서 사용하는 콘텐츠 검수 관련 API입니다.
Base URL: /ai/business/contents
인증 정보
| 항목 | 값 |
|---|---|
| 인증 필요 | 예 |
| 인증 방식 | JWT Bearer Token |
엔드포인트 목록
| 메서드 | 경로 | 설명 | 인증 |
|---|---|---|---|
PUT | /ai/business/contents/guideline/{collabNo} | 가이드라인 저장/생성 | 필요 |
PUT | /ai/business/contents/guideline/empty/{collabNo} | 가이드라인 빈 템플릿 요청 | 필요 |
GET | /ai/business/contents/review/{reviewId}/detail | 검수 라운드 상세 조회 | 필요 |
POST | /ai/business/contents/{itemId}/feedbacks | 피드백 추가/검수 완료 (개별) | 필요 |
POST | /ai/business/contents/review/{reviewId}/feedbacks/bulk | 벌크 피드백 추가/검수 완료 | 필요 |
POST | /ai/business/contents/upload-date | 업로드일 선택 | 필요 |
POST | /ai/business/contents/review-request | 검수 추가 요청 | 필요 |
POST | /ai/business/contents/final-submissions/bulk | 벌크 최종 제출물 조회 | 필요 |
POST | /ai/business/contents/final-submission/{applicationId}/re-request | 최종제출물 재요청 | 필요 |
API 상세
가이드라인 저장/생성 (Upsert)
가이드라인 데이터를 저장합니다. 없으면 생성, 있으면 병합합니다.
수정 제한 조건
- 가이드라인이 이미 존재하는 경우, 모집 시작 전에만 수정 가능합니다.
- 모집이 시작된 이후(
CREATOR_RECRUIT단계 이후)에는 수정이 불가능합니다. - 수정 가능 단계:
CAMPAIGN_REVIEW,CAMPAIGN_PAYMENT,CAMPAIGN_GUIDELINE
초안 검수 vs 완성본 저장
isFirst=true: 초안 검수 모드. 가이드라인 상태가DRAFT로 설정되며,hasGuideline=false유지isFirst=false(기본값): 완성본 저장 모드. 가이드라인 상태가COMPLETED로 설정되며,hasGuideline=true- 완성본 저장 시에만 캠페인 단계가
CREATOR_RECRUIT로 변경됩니다 (모집 시작)
HTTP 요청
PUT /ai/business/contents/guideline/{collabNo}?isFirst=false
Authorization: Bearer {access_token}
Content-Type: application/jsonPath Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
collabNo | int | 예 | 캠페인 번호 |
Query Parameters
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
isFirst | boolean | 아니오 | false | true: 초안 검수 (DRAFT), false: 완성본 저장 (COMPLETED) |
Request Body
가이드라인은 4단계 Wizard 구조로 되어 있습니다. 각 단계별로 부분 저장이 가능하며, 기존 데이터와 병합됩니다.
{
"contentTypes": ["VIDEO", "CAPTION", "HASHTAG"],
"basicInfo": {
"productUrl": "https://example.com/product",
"contentConcepts": [
{
"code": "STORY_TELLING",
"referenceUrl": "https://instagram.com/reel/xxx",
"referenceDescription": "이 영상처럼 자연스러운 썰풀기 스타일로"
}
],
"keywords": [
{ "text": "여름선크림", "isRequired": true }
],
"titleExample": "여름 필수템! 촉촉한 선크림 추천"
},
"contentDetailInfo": {
"categoryCode": "BEAUTY",
"basicShots": [
{
"code": "TEXTURE_SHOT",
"appealPoint": "크리미한 제형과 빠른 흡수력을 보여주세요",
"subOption": null
}
],
"additionalOptions": [
{ "code": "BEFORE_AFTER", "additionalPrice": 50000, "inputText": null }
],
"etcOptions": []
},
"marketingInfo": {
"requiredPoints": ["SPF50+ PA++++ 자외선 차단", "촉촉한 수분감"],
"optionalPoints": ["비건 인증"],
"videoMarketingOptions": {
"promotion": {
"referenceUrl": "https://example.com/promo",
"description": "6월 한정 20% 할인 진행 중"
},
"brandAccountTag": {
"accountName": "@glowb_beauty",
"tagMethods": ["PERSON_TAG", "CAPTION_TAG"],
"saveAsDefault": true
},
"sponsorLabel": {
"accountName": "@glowb_official"
},
"collaborator": {
"accountName": "@glowb_collab",
"tagMethods": null,
"saveAsDefault": false
},
"useAutoDm": false,
"productLinkShare": {
"enabled": true,
"shareMethods": ["dm", "profileLink"],
"shareLink": "https://example.com/product",
"shareDuration": 2,
"shareDurationUnit": "week",
"priceIncrease": 50000
}
},
"hashtags": ["#글로브뷰티", "#여름선크림"]
},
"detailPageInfo": {
"brandLogo": {
"imageUrl": "https://s3.../brand-logo.png",
"saveAsDefault": true
},
"thumbnailUrl": "https://s3.../thumbnail.jpg",
"detailImageUrls": ["https://s3.../detail-1.jpg", "https://s3.../detail-2.jpg"]
}
}Request Body 필드 상세 설명
전체 필드 상세는 가이드라인 조회 API 문서를 참고하세요.
응답
성공 응답 (200 OK) - 완성본 저장 (isFirst=false)
{
"status": 200,
"code": null,
"message": "가이드라인이 저장되었습니다.",
"data": {
"collabNo": 123,
"isFirst": false,
"guidelineStatus": "COMPLETED",
"message": "가이드라인이 저장되었습니다."
}
}성공 응답 (200 OK) - 초안 검수 (isFirst=true)
{
"status": 200,
"code": null,
"message": "가이드라인이 저장되었습니다.",
"data": {
"collabNo": 123,
"isFirst": true,
"guidelineStatus": "DRAFT",
"message": "가이드라인이 저장되었습니다."
}
}Response 스키마
| 필드 | 타입 | 설명 |
|---|---|---|
collabNo | int | 캠페인 번호 |
isFirst | boolean | 초안 검수 여부 |
guidelineStatus | string | 가이드라인 상태 (REQUESTED: 요청됨, DRAFT: 초안, COMPLETED: 완성) |
message | string | 결과 메시지 |
에러 응답 (400 Bad Request) - 모집 시작 후 수정 시도
{
"status": 400,
"code": "GUIDELINE_MODIFICATION_NOT_ALLOWED",
"message": "모집이 시작된 이후에는 가이드라인을 수정할 수 없습니다.",
"data": null
}가이드라인 빈 템플릿 요청
관리자에게 빈 가이드라인 템플릿 작성을 요청합니다. 가이드라인 상태가 REQUESTED로 설정되고, 캠페인 진행상태가 CAMPAIGN_GUIDELINE 단계로 변경됩니다.
상태 변경
guidelineStatus→REQUESTED(빈 템플릿 요청됨)campaignSubStep→CAMPAIGN_GUIDELINE(가이드라인 작성 단계)
HTTP 요청
PUT /ai/business/contents/guideline/empty/{collabNo}
Authorization: Bearer {access_token}Path Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
collabNo | int | 예 | 캠페인 번호 |
응답
성공 응답 (200 OK)
{
"status": 200,
"code": null,
"message": "가이드라인 빈템플릿 요청이 완료 되었습니다.",
"data": {
"collabNo": 123,
"guidelineStatus": "REQUESTED"
}
}Response 스키마
| 필드 | 타입 | 설명 |
|---|---|---|
collabNo | int | 캠페인 번호 |
guidelineStatus | string | 가이드라인 상태 (REQUESTED: 빈 템플릿 요청됨) |
검수 라운드 상세 조회
검수 라운드의 모든 제출물과 각 제출물의 히스토리, 피드백을 포함하여 조회합니다.
HTTP 요청
GET /ai/business/contents/review/{reviewId}/detail
Authorization: Bearer {access_token}Path Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
reviewId | long | 예 | 검수 라운드 ID |
응답
성공 응답 (200 OK)
{
"status": 200,
"code": null,
"message": "검수 라운드 상세 조회가 완료되었습니다.",
"data": {
"reviewId": 1,
"applicationId": 100,
"reviewRound": 1,
"status": "REVIEWING",
"uploadApproved": false,
"feedbackDeadline": "2024-01-20T10:30:00",
"createdAt": "2024-01-15T10:30:00",
"creatorName": "홍길동",
"creatorEmail": "creator@example.com",
"submissionItems": [
{
"id": 1,
"itemType": "VIDEO",
"filePath": "https://s3.../video.mp4",
"editorState": null,
"comment": null,
"status": "REVIEWING",
"isSubmitted": true,
"submittedAt": "2024-01-16T14:00:00",
"uploadedAt": "2024-01-16T14:00:00",
"currentVersion": 2,
"createdAt": "2024-01-15T10:30:00",
"histories": [...],
"feedbacks": [...]
}
],
"unreflectedFeedbackIds": [1, 2, 3]
}
}Response 스키마
| 필드 | 타입 | 설명 |
|---|---|---|
reviewId | long | 검수 라운드 ID |
applicationId | long | CampaignApplication ID |
reviewRound | int | 검수 라운드 (1: 1차, 2: 2차) |
status | string | 검수 상태 |
uploadApproved | boolean | 업로드 승인 여부 |
feedbackDeadline | datetime | 피드백 마감일 |
createdAt | datetime | 생성일시 |
creatorName | string | 크리에이터명 |
creatorEmail | string | 크리에이터 이메일 |
submissionItems | array | 제출물 목록 (히스토리, 피드백 포함) |
unreflectedFeedbackIds | array<long> | 반영되지 않은 피드백 ID 목록 (nullable) |
unreflectedFeedbackIds 필드
기업이 "피드백 미반영" 검수 추가 요청 시 지정한 피드백 ID 목록입니다.
- 값이 있으면: 해당 피드백들을 하이라이트 표시하여 크리에이터가 미반영한 피드백 확인 가능
- 값이
null이면: 검수 추가 요청이 없거나 OUTSIDE_GUIDELINE 유형인 경우
피드백 추가 / 검수 완료
제출물에 피드백을 추가하거나 검수를 완료 처리합니다.
- 피드백 내용이 비어있고 파일도 없으면 → 검수 완료 (APPROVED)
- 피드백 내용이나 파일이 있으면 → 재제출 요청 (REJECTED) + 피드백 마감일 설정 (4일 후)
HTTP 요청
POST /ai/business/contents/{itemId}/feedbacks
Authorization: Bearer {access_token}
Content-Type: multipart/form-dataPath Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
itemId | long | 예 | 제출물 ID |
Request Parts
| 파트 | 타입 | 필수 | 설명 |
|---|---|---|---|
request | FeedbackDto.CreateRequest | 예 | 피드백 요청 JSON |
files | file[] | 아니오 | 첨부 파일 (이미지/영상) |
request JSON 예시
{
"content": "영상 중간 부분 재촬영 필요합니다.",
"mediaFeedbacks": [
{
"comment": "이 부분 조명이 어둡습니다",
"startTime": 15,
"endTime": 20
}
]
}벌크 피드백 추가 / 검수 완료
검수 라운드의 모든 아이템에 대해 피드백 추가 또는 검수 완료를 일괄 처리합니다.
기존 개별 API의 문제점 해결
기존 개별 API(POST /{itemId}/feedbacks)는 아이템마다 검수 횟수(count)가 증가하여, 여러 아이템을 순차적으로 처리할 때 "검수 횟수 초과" 오류가 발생할 수 있습니다.
벌크 API는 모든 아이템을 한 번에 처리하고, 검수 횟수는 라운드 단위로 1번만 증가합니다.
처리 규칙
- 각 아이템별로
feedback이 null이고 파일이 없으면 → 승인 (APPROVED) feedback내용이나 파일이 있으면 → 재제출 요청 (REJECTED)- 검수 횟수(count)는 전체 처리 후 1번만 증가
- 피드백 마감일은 가장 마지막 피드백 기준으로 4일 후 설정
HTTP 요청
POST /ai/business/contents/review/{reviewId}/feedbacks/bulk
Authorization: Bearer {access_token}
Content-Type: multipart/form-dataPath Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
reviewId | long | 예 | 검수 라운드 ID |
Request Parts
| 파트 | 타입 | 필수 | 설명 |
|---|---|---|---|
request | BulkFeedbackDto.Request | 예 | 벌크 피드백 요청 JSON |
files | file[] | 아니오 | 첨부 파일 (모든 아이템 파일을 배열로 전달) |
request JSON 구조
{
"items": [
{
"itemId": 244,
"feedback": null,
"highlightedText": null,
"selection": null,
"fileIndices": [],
"mediaList": []
},
{
"itemId": 245,
"feedback": null,
"highlightedText": null,
"selection": null,
"fileIndices": [],
"mediaList": []
},
{
"itemId": 246,
"feedback": "피드백 내용입니다. 이 부분을 수정해주세요.",
"highlightedText": "수정 필요한 텍스트",
"selection": "{\"anchor\":{},\"focus\":{}}",
"fileIndices": [0, 1],
"mediaList": [
{
"mediaType": "IMAGE",
"startTime": null,
"endTime": null,
"comment": "이 부분 참고"
},
{
"mediaType": "VIDEO",
"startTime": 10.5,
"endTime": 15.0,
"comment": "이 구간 재촬영 필요"
}
]
}
]
}ItemFeedback 필드 설명
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
itemId | long | 예 | 제출물 아이템 ID |
feedback | string | 아니오 | 피드백 내용 (null이면 승인 처리) |
highlightedText | string | 아니오 | 하이라이트된 텍스트 |
selection | string | 아니오 | 선택 영역 정보 (JSON) |
fileIndices | int[] | 아니오 | files 배열에서의 인덱스 목록 |
mediaList | MediaMeta[] | 아니오 | 각 파일에 대한 메타데이터 |
MediaMeta 필드 설명
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
mediaType | string | 아니오 | 미디어 타입 (IMAGE, VIDEO) |
startTime | double | 아니오 | 영상 시작 시간 (초) |
endTime | double | 아니오 | 영상 끝 시간 (초) |
comment | string | 아니오 | 미디어에 대한 코멘트 |
응답
성공 응답 (200 OK)
{
"status": 200,
"code": null,
"message": "3건의 제출물이 승인되었습니다.",
"data": {
"success": true,
"approvedCount": 3,
"rejectedCount": 0,
"processedItemIds": [244, 245, 246],
"message": "3건의 제출물이 승인되었습니다."
}
}피드백 포함 응답
{
"status": 200,
"code": null,
"message": "2건 승인, 1건 피드백 추가가 완료되었습니다.",
"data": {
"success": true,
"approvedCount": 2,
"rejectedCount": 1,
"processedItemIds": [244, 245, 246],
"message": "2건 승인, 1건 피드백 추가가 완료되었습니다."
}
}에러 응답 (404) - 검수 횟수 초과
{
"status": 404,
"code": "INVALID_DATA",
"message": "검수 횟수를 초과했습니다. (현재: 1/1) 검수 추가 요청이 필요합니다.",
"data": null
}검수 추가 요청
검수 횟수를 초과한 경우 추가 검수를 요청합니다.
검수 추가 요청 유형
FEEDBACK_NOT_REFLECTED: 피드백/가이드라인이 반영되지 않음 (크레딧 차감 없음)OUTSIDE_GUIDELINE: 가이드라인 외 추가 요청 (50,000 크레딧 차감)
HTTP 요청
POST /ai/business/contents/review-request
Authorization: Bearer {access_token}
Content-Type: application/jsonRequest Body
{
"reviewId": 123,
"requestType": "FEEDBACK_NOT_REFLECTED",
"unreflectedFeedbackIds": [1, 2, 3],
"requestContent": "캡션에 필수 키워드가 누락되었습니다."
}Request Body 필드 설명
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
reviewId | long | 예 | 검수 라운드 ID |
requestType | string | 예 | 요청 유형 (FEEDBACK_NOT_REFLECTED, OUTSIDE_GUIDELINE) |
unreflectedFeedbackIds | array<long> | 아니오 | 반영되지 않은 피드백 ID 목록 (FEEDBACK_NOT_REFLECTED 시 필요) |
requestContent | string | 아니오 | 추가 요청 내용 |
상태 변경 처리
검수 추가 요청 시 다음 상태가 자동으로 변경됩니다:
-
ContentReview
maxFeedbackCount+1 증가status→PENDING(다시 피드백 가능 상태)
-
unreflectedFeedbackIds에 해당하는 피드백 (FEEDBACK_NOT_REFLECTED 유형)
resolved→false(미해결 상태로 초기화)checkedByCreator→false(체크 상태 초기화)
-
해당 피드백이 속한 제출물
status→REJECTED(재제출 필요 상태)
응답
성공 응답 (200 OK)
{
"status": 200,
"code": null,
"message": "검수 추가 요청이 등록되었습니다. (피드백 횟수: 2회)",
"data": {
"id": 1,
"reviewId": 123,
"requestType": "FEEDBACK_NOT_REFLECTED",
"creditDeducted": 0,
"message": "검수 추가 요청이 등록되었습니다. (피드백 횟수: 2회)",
"createdAt": "2024-01-20T10:30:00"
}
}OUTSIDE_GUIDELINE 유형 응답
{
"status": 200,
"code": null,
"message": "검수 추가 요청이 등록되었습니다. (크레딧 차감: 50,000원, 피드백 횟수: 2회)",
"data": {
"id": 2,
"reviewId": 123,
"requestType": "OUTSIDE_GUIDELINE",
"creditDeducted": 50000,
"message": "검수 추가 요청이 등록되었습니다. (크레딧 차감: 50,000원, 피드백 횟수: 2회)",
"createdAt": "2024-01-20T10:30:00"
}
}업로드일 선택
최종 업로드 일정을 선택합니다.
HTTP 요청
POST /ai/business/contents/upload-date
Authorization: Bearer {access_token}
Content-Type: application/jsonRequest Body
{
"applicationId": 123,
"selectionType": "OPTIMAL",
"uploadDate": "2024-02-01"
}selectionType 옵션
| 타입 | 설명 | 필수 필드 |
|---|---|---|
OPTIMAL | 3일 이내 최적 날짜에 게시 | uploadDate |
CANDIDATE | 기업이 후보 지정 (관리자에게 이메일 전송) | candidateDates |
CANDIDATE 타입 예시
{
"applicationId": 123,
"selectionType": "CANDIDATE",
"candidateDates": ["2024-02-01", "2024-02-03", "2024-02-05"]
}벌크 최종 제출물 조회
여러 CampaignApplication ID에 대한 최종 제출물을 벌크로 조회합니다.
HTTP 요청
POST /ai/business/contents/final-submissions/bulk
Authorization: Bearer {access_token}
Content-Type: application/jsonRequest Body
{
"applicationIds": [1, 2, 3, 4, 5]
}응답
성공 응답 (200 OK)
{
"status": 200,
"code": null,
"message": "벌크 최종 제출물 조회가 완료되었습니다.",
"data": {
"submissions": [
{
"applicationId": 1,
"creatorName": "인플루언서A",
"uploadLink": "https://instagram.com/p/...",
"partnershipCode": "ABC123",
"files": [
{
"fileUrl": "https://...",
"fileName": "final.mp4",
"fileType": "VIDEO"
}
]
}
]
}
}최종제출물 재요청
크리에이터에게 최종제출물 항목을 선택하여 재요청합니다.
재요청 특징
- 여러 번 재요청 가능 (새 재요청은 기존 재요청을 덮어씀)
- 크리에이터가 재제출해도 재요청 이력은 유지됨
HTTP 요청
POST /ai/business/contents/final-submission/{applicationId}/re-request
Authorization: Bearer {access_token}
Content-Type: application/jsonPath Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
applicationId | long | 예 | 캠페인 신청 ID |
Request Body
{
"types": ["CONTENT_LINK", "CLEAN_FILE"]
}재요청 타입 (FinalSubmissionType)
| 타입 | 설명 |
|---|---|
CONTENT_LINK | 업로드한 콘텐츠 링크 |
PARTNERSHIP_CODE | 파트너십 코드 |
CLEAN_FILE | 클린본 파일 |
FINAL_FILE | 최종본 파일 |
응답
성공 응답 (200 OK)
{
"status": 200,
"code": null,
"message": "재요청이 등록되었습니다.",
"data": {
"id": 1,
"applicationId": 123,
"contentLink": "https://instagram.com/p/...",
"partnershipCode": "ABC123",
"cleanFilePath": null,
"finalFilePath": null,
"isSubmitted": true,
"submittedAt": "2024-01-15T10:30:00",
"requiredTypes": ["CONTENT_LINK", "PARTNERSHIP_CODE", "CLEAN_FILE", "FINAL_FILE"],
"hasReRequest": true,
"reRequestTypes": ["CONTENT_LINK", "CLEAN_FILE"],
"reRequestedAt": "2024-01-20T14:00:00"
}
}Response 스키마 (재요청 관련 필드)
| 필드 | 타입 | 설명 |
|---|---|---|
hasReRequest | boolean | 재요청 존재 여부 |
reRequestTypes | array<string> | 재요청 항목 목록 |
reRequestedAt | datetime | 재요청 일시 |
사용 예시
피드백 추가
curl -X POST https://api.glowb.io/ai/business/contents/1/feedbacks \
-H "Authorization: Bearer {token}" \
-F 'request={"content":"재촬영 필요","mediaFeedbacks":[{"comment":"조명 문제","startTime":15,"endTime":20}]}' \
-F "files=@reference.jpg"업로드일 선택
curl -X POST https://api.glowb.io/ai/business/contents/upload-date \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"applicationId": 123,
"selectionType": "OPTIMAL",
"uploadDate": "2024-02-01"
}'최종제출물 재요청
curl -X POST https://api.glowb.io/ai/business/contents/final-submission/123/re-request \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{
"types": ["CONTENT_LINK", "CLEAN_FILE"]
}'