Admin APIAdmin Dashboard API
POST /ai/admin/dashboard/{campaignNo}/price-negotiations
협업 단가 전송
협업 단가 전송 (벌크)
선택된 크리에이터들에게 단가 제안을 전송합니다.
- CampaignApplication이 없으면 자동 생성(ADMIN_ADDED)되고, PriceNegotiation이 PENDING 상태로 생성됩니다.
- 각 크리에이터별로 독립적으로 처리되며, 일부 실패 시에도 나머지는 정상 처리됩니다.
중복 방지 정책
동일 크리에이터에게 중복 전송을 방지하기 위해 다음 조건을 확인합니다:
- PENDING 상태: 이미 협상 진행 중이면 재요청 불가
- ACCEPT 상태: 이미 수락된 협상이 있으면 재요청 불가
- REJECT 상태: 거절된 경우에만 재협상 가능
HTTP 요청
POST /ai/admin/dashboard/{campaignNo}/price-negotiations
Authorization: Bearer {access_token}
Content-Type: application/jsonPath Parameters
| 파라미터 | 타입 | 필수 | 설명 |
|---|---|---|---|
campaignNo | Long | 예 | 캠페인 번호 |
Request Body
| 필드 | 타입 | 필수 | 설명 |
|---|---|---|---|
items | Array | 예 | 단가 제안 목록 |
items[].applicationId | Long | 조건부 | 기존 신청 ID (있으면 우선 사용) |
items[].influenceNo | Integer | 조건부 | 인플루언서 번호 (applicationId 없을 때 필수) |
items[].proposedPrice | Long | 조건부 | 제안 단가 (캠페인 통화). priceMode 를 보내면 무시됨 |
priceMode | String | 아니오 | 제안가 산정 방식. 보내면 서버가 크리에이터별로 계산 (아래 표) |
customPriceKrw | Long | 조건부 | priceMode=CUSTOM 일 때 관리자 입력 금액 (원) |
priceMode — 서버 계산
제안가는 크리에이터가 보는 캠페인 통화 기준입니다. 원화로만 있는 값(랭킹가·직접 입력)은 외화 캠페인(currency 가 KRW 가 아닌 캠페인)에서 캠페인 환율(외화 1단위당 원)로 나눠 바꿉니다. 끝자리는 통화와 관계없이 소수점만 버립니다 (기존 프론트 계산과 같은 규칙이라 국내 결과는 동일).
| priceMode | 국내(KRW) | 외화(USD·JPY 등) | 재료 없을 때 |
|---|---|---|---|
RANK | 랭킹가 그대로 | 랭킹가(원) ÷ 환율. 미국 틱톡은 USD 랭크가를 그대로 사용 | NO_RANK_PRICE, 환율 없으면 RATE_NOT_SET |
DESIRED | 희망가 그대로 | 희망가 그대로 (환율 불필요) | NO_DESIRED_PRICE |
DESIRED_MINUS_10 | 희망가 × 0.9 | 희망가 × 0.9 (환율 불필요) | NO_DESIRED_PRICE |
DESIRED_MINUS_20 | 희망가 × 0.8 | 희망가 × 0.8 (환율 불필요) | NO_DESIRED_PRICE |
CUSTOM | customPriceKrw 그대로 | customPriceKrw ÷ 환율 | INVALID_PRICE, 환율 없으면 RATE_NOT_SET |
- 재료가 없는 크리에이터는 다른 값으로 대체하지 않고
failedItems로 돌려주며, 나머지는 정상 전송합니다. - 계산 결과가 0 이면
INVALID_PRICE로 실패합니다 (예: 1,000원 ÷ 1,380). priceMode를 생략하면 기존처럼items[].proposedPrice를 그대로 저장합니다.- 실제 저장된 금액은 응답
sentPrices로 확인하고, 후속 알림·화면 갱신은 이 값을 사용하세요.
요청 예시 (priceMode)
{
"priceMode": "DESIRED_MINUS_10",
"items": [
{ "applicationId": 123 },
{ "applicationId": 124 }
]
}요청 예시 (기존 방식)
{
"items": [
{
"applicationId": 123,
"proposedPrice": 500000
},
{
"influenceNo": 456,
"proposedPrice": 300000
}
]
}응답
성공 응답 (200 OK)
{
"status": 200,
"code": null,
"message": "3명에게 단가 제안이 전송되었습니다.",
"data": {
"success": true,
"successApplicationIds": [123, 124, 125],
"sentPrices": { "123": 49, "124": 45, "125": 90 },
"phoneNotFoundApplicationIds": [125],
"failedItems": [],
"message": "3명에게 단가 제안이 전송되었습니다. (전화번호 없음: 1명)"
}
}일부 실패 응답 (200 OK)
{
"status": 200,
"code": null,
"message": "2명 성공, 1명 실패",
"data": {
"success": false,
"successApplicationIds": [123, 124],
"phoneNotFoundApplicationIds": [],
"failedItems": [
{
"applicationId": 125,
"influenceNo": null,
"failureCode": "CREATE_FAILED",
"failureMessage": "이미 협상 진행 중입니다. 크리에이터의 응답을 기다려주세요."
}
],
"message": "2명 성공, 1명 실패"
}
}에러 응답 예시
중복 협상 (PENDING 상태)
failedItems에 포함되는 형태:
{
"failureCode": "CREATE_FAILED",
"failureMessage": "이미 협상 진행 중입니다. 크리에이터의 응답을 기다려주세요."
}수락된 협상 재시도 (ACCEPT 상태)
failedItems에 포함되는 형태:
{
"failureCode": "CREATE_FAILED",
"failureMessage": "이미 수락된 협상이 있습니다. 새로운 협상을 시작할 수 없습니다."
}존재하지 않는 신청
failedItems에 포함되는 형태:
{
"failureCode": "APPLICATION_NOT_FOUND",
"failureMessage": "존재하지 않는 신청입니다."
}캠페인 불일치
failedItems에 포함되는 형태:
{
"failureCode": "CAMPAIGN_MISMATCH",
"failureMessage": "캠페인이 일치하지 않습니다."
}priceMode 계산 실패
| failureCode | 사유 |
|---|---|
NO_DESIRED_PRICE | 희망가가 없음 (불러오기로 추가한 크리에이터는 희망가를 먼저 저장) |
NO_RANK_PRICE | 랭킹가가 없거나 0 |
RATE_NOT_SET | 외화 캠페인인데 환율 미설정 (RANK·CUSTOM) |
INVALID_PRICE | 직접 입력 금액이 없거나 계산 결과가 0 |
캠페인에서 제외된 크리에이터
크리에이터가 가격 협상 응답 시 캠페인 참여를 거절(decline)하면 신청이 제외 처리(ELIMINATED)됩니다. 이 상태의 신청에는 단가를 재전송할 수 없습니다. 다시 초대하려면 관리자가 먼저 선정 상태를 복구해야 합니다.
failedItems에 포함되는 형태:
{
"failureCode": "ELIMINATED",
"failureMessage": "캠페인에서 제외된 크리에이터입니다. 선정 상태를 먼저 복구해야 단가를 전송할 수 있습니다."
}응답 필드 설명
| 필드 | 타입 | 설명 |
|---|---|---|
success | Boolean | 전체 성공 여부 (모든 항목 성공 시 true) |
successApplicationIds | Array<Long> | 성공한 신청 ID 목록 |
sentPrices | Object | 성공한 신청 ID → 실제 저장된 제안가 (캠페인 통화) |
phoneNotFoundApplicationIds | Array<Long> | 전화번호가 없는 신청 ID 목록 (SMS 발송 불가) |
failedItems | Array | 실패한 항목 목록 |
failedItems[].applicationId | Long | 실패한 신청 ID |
failedItems[].influenceNo | Integer | 실패한 인플루언서 번호 |
failedItems[].failureCode | String | 실패 코드 |
failedItems[].failureMessage | String | 실패 사유 |