단계 1. 모델 API 설계 - 100-hours-a-week/KTB4-1st-wiki GitHub Wiki

API 설계 및 구현 문서

목차

  1. API 엔드포인트
  2. API 상세 명세
  3. 서비스 구조 및 연동 관계
  4. API 호출 테스트

1. API 엔드포인트

사용하는 API의 엔드포인트와 기능을 정리한다.

기능 번호 Method Endpoint 기능
기능1 POST /api/vision/analyze-item 상품 이미지 1~3장으로 검열 + 제품 식별 + 등록용 제목/설명 초안 생성
기능2 POST /api/moderation/check-text 최종 제목·내용 검열
기능3 POST /api/value/estimate-price 시세 검색 및 가치 범위(기준가/하한/상한) 산정
기능4 POST /api/chatbot/ask 정책 문서 검색(RAG) 기반 챗봇 답변 및 화면 이동 제안
기능5 POST /api/search/rank-candidates 취향 신호 기반 후보 물건 순위 매김
기능6 POST /api/chat/draft-opening-message 첫 메시지 초안 생성
기능7 POST /api/chat/refine-message 채팅 메시지 AI 다듬기

2. API 상세 명세

각 API의 입력값과 출력값을 정리한다.

2.1 POST /api/vision/analyze-item

기능

상품 이미지 1~3장을 받아 (1) 부적절 콘텐츠 검열, (2) 제품 식별, (3) 등록용 제목·상세설명 초안 작성을 한 번의 Claude API 호출로 처리한다.

Request

필드 타입 필수 설명
images file[] O 상품 이미지 배열 (jpg/jpeg/png/webp, 최대 5mb, 1~3장)

요청 예시 (multipart/form-data — JSON 아님)

images: hetbahn_pack1.jpg, hetbahn_pack2.jpg

Response

필드 타입 필수 설명
isAppropriate boolean O 사진 검열 결과
rejectionReason string - 부적절한 경우 사유, 적절하면 null
title string O (confidence < 0.25이면 null) 게시글 제목
content string O (confidence < 0.25이면 null) 게시글 상세내용
confidence float O 0.0~1.0 확신도

응답 예시

{
  "isAppropriate": true,
  "rejectionReason": null,
  "title": "햇반 210g 새 제품 4개 교환해요",
  "content": "햇반 210g 새 제품입니다. 24개입 세트 중 4개를 교환하려고요. 미개봉 상태입니다.",
  "confidence": 0.97
}

내부 처리

claude-sonnet-5 (비전 지원). 사진 1~3장을 함께 보고 검열(금지 품목/상품과 무관한 사진/서로 다른 물건 혼합 여부, 단 세트상품·클로즈업샷은 정상 처리), 제품 식별(불확실하면 null, confidence 표시), 등록용 title(30자 이내)·content(2000자 이내, 가격 언급 금지) 작성을 지시하는 프롬프트 사용. Claude 응답의 isAppropriate/confidence 값에 따라 AI 서버가 200/422/500으로 매핑한다.

에러 케이스

상태 코드 상황 응답 예시
400 이미지 파일이 없거나 지원하지 않는 형식 {"error": "invalid_input"}
413 이미지 파일 하나당 5mb 초과 {"error": "file_too_large"}
422 검열된 실패 (금지 품목 / 상품과 무관한 사진 / 서로 다른 물건 혼합 등) {"error": "content_rejected"}
500 Claude API 오류 (요청 한도 초과, 응답 지연 등) {"error": "internal_error"}

실패 시 처리 정책

  • 요청이 실패하면(500 등) 검열 결과를 알 수 없으므로 등록을 진행할 수 없다. 재업로드/재시도를 안내한다.
  • confidence < 0.25면 title/content를 null로 두고 사용자가 직접 입력하게 한다. 등록 절차 자체는 막지 않는다.

2.2 POST /api/moderation/check-text

기능

최종 확정된 제목과 상세내용을 검열해 게시 가능 여부를 판단한다.

Request

필드 타입 필수 설명
title string O 최종 제목
content string O 최종 상세 내용

요청 예시

{
  "title": "햇반 210g 새 제품 4개 교환해요",
  "content": "24개입 세트로 구매했는데 다 못 먹을 것 같아서 4개만 교환하려고요. 미개봉 새 제품이고 유통기한은 2027년 6월까지예요."
}

Response

성공(200) 시 응답 본문 없음. 상태 코드만으로 통과 여부를 판단하며, 422/500일 때만 본문의 message를 확인한다.

내부 처리

claude-haiku-5 (텍스트 전용, 기능1보다 가볍고 빠른 모델). 욕설/비속어/혐오 표현, 금지 품목 언급, 사기성 문구, 개인 연락처 노출, 광고성 스팸, 서로 무관한 여러 물건 혼재 — 6가지 기준으로 검열하고 애매하면 차단하도록 지시하는 프롬프트 사용. isAppropriate 값에 따라 200/422로 매핑한다.

에러 케이스

상태 코드 상황 응답 예시
400 제목 또는 내용 누락 {"error": "invalid_input"}
422 검열 실패 (욕설•혐오, 금지 품목, 사기성 문구, 개인 연락처, 스팸, 물건 혼합 등) {"error": "content_rejected"}
500 Claude API 오류(요청 한도 초과, 응답 지연, 파싱 실패 등) {"error": "internal_error"}

실패 시 처리 정책

  • 요청 실패 시(500 등) 등록을 진행할 수 없다. 재시도를 안내한다.
  • 422는 명확한 거절이므로 message를 안내하고 텍스트 수정을 유도한다.
  • 동기 처리 — 통과(200)해야만 다음 단계(가치 평가, 2.3)로 진행된다.

2.3 POST /api/value/estimate-price

기능

최종 제목·내용을 바탕으로 web_search로 시세를 조사하고, 사용자 선호도를 반영해 가치 범위를 산정한다.

Request

필드 타입 필수 설명
title string O 최종 제목
content string O 최종 상세 내용
valueTolerance float O "가치 유연성" 슬라이더 값 (0.0~1.0)
tradeSpeed float O "판매 속도" 슬라이더 값 (0.0~1.0)

요청 예시

{
  "title": "햇반 210g 작은공기",
  "content": "24개입 세트로 구매했는데 다 못 먹을 것 같아서 4개만 교환하려고요. 미개봉 새 제품이에요.",
  "valueTolerance": 0.6,
  "tradeSpeed": 0.4
}

Response

필드 타입 필수 설명
referenceUnitPrice int O 검색/추정 기반 기준가
minUnitPrice int O 사용자 선호도 반영한 하한
maxUnitPrice int O 기준가와 동일 (상한)
priceSource string O "new" 또는 "estimated"
confidence float O (priceSource가 "new"이면 null) estimated일 때만 의미 있음

응답 예시

{
  "referenceUnitPrice": 1300,
  "minUnitPrice": 1090,
  "maxUnitPrice": 1300,
  "priceSource": "new",
  "confidence": null
}

내부 처리

claude-sonnet-5 (web_search 도구 활성화). 여러 쇼핑몰에서 가격을 검색해 개당 가격으로 통일, 검색 결과가 없으면 자체 추정값 + source: "estimated" 반환하도록 지시. Claude가 반환한 prices 배열은 이후 서버 코드(LLM 아님)가 직접 계산한다.

1. prices가 3개 이상이면 이상치 제거 (IQR 기준). 제거 결과 0개가 되면 원본 전체 사용.
   2개 이하면 이상치 제거 생략.
2. 평균 → referenceUnitPrice (10원 단위 반올림)
3. discountRate = (valueTolerance × 0.7 + tradeSpeed × 0.3) × 0.3  (최대 할인폭 30%)
4. maxUnitPrice = referenceUnitPrice
   minUnitPrice = referenceUnitPrice × (1 − discountRate), 10원 단위 반올림

에러 케이스

상태 코드 상황 응답 예시
400 title / content / valueTolerance / tradeSpeed 중 누락 {"error": "invalid_input"}
500 Claude API 오류(요청 한도 초과, 응답 지연, 파싱 실패 등) internal_error

실패 시 처리 정책

  • 기능2 통과 직후 동기로 호출된다. 실패 시(500) 가치 데이터를 알 수 없으므로 등록을 차단하고 재시도를 안내한다.
  • 검색 실패해도 Claude가 estimated로 값을 채우므로, 값이 비는 경우는 API 호출 자체가 실패했을 때(500)뿐이다.
  • 2.2가 실패(422/500)하면 이 요청은 트리거되지 않는다.

2.4 POST /api/chatbot/ask

기능

사용자 메시지를 받아 관련 정책 문서를 검색(RAG)하고, 정책 답변 생성 또는 화면 이동을 제안한다.

Request

필드 타입 필수 설명
message string O 사용자 채팅
history array X 이전 대화 목록 (최근 10개로 제한)

요청 예시

{
  "message": "탈퇴는 어떻게 해요?",
  "history": []
}

Response

필드 타입 필수 설명
answer string O LLM 답변
action object / null - 화면 이동 정보 (label, screen enum), 없으면 null

응답 예시

{
  "answer": "탈퇴는 마이페이지 > 계정 설정 > 탈퇴하기에서 진행하실 수 있어요. 탈퇴 후 7일 뒤 데이터가 완전히 삭제돼요.",
  "action": { "label": "마이페이지로 이동", "screen": "MY_PAGE" }
}

내부 처리

claude-sonnet-5. 정책 문서 13개 섹션은 사전 임베딩되어 있으며, 사용자 메시지를 임베딩해 상위 1~2개 섹션을 검색(RAG)한 뒤 그 내용만 근거로 답변하거나, 화면 이동 요청이면 지정된 enum(MY_PAGE/ITEM_LIST/ITEM_ADD/CHAT_LIST/GROUP_MANAGE/INTEREST_LIST/INQUIRY) 중 하나로 이동 버튼을 제안하도록 지시. 문서에 없는 내용은 추측 금지, 답을 못 찾으면 1:1 문의로 안내.

에러 케이스

상태 코드 상황 응답 예시
400 입력창에 아무 입력이 없음 {"error": "invalid_input"}
500 Claude API 오류(요청 한도 초과, 응답 지연, 파싱 실패 등) internal_error

실패 시 처리 정책

  • 실패 시(500) 채팅창에 오류 메시지와 재전송 버튼 표시. 다른 화면에 영향 없음.
  • 정책 문서에서 못 찾은 경우는 에러가 아니라 200 정상 응답(1:1 문의 안내)으로 처리.
  • 물건 등록 흐름(2.1~2.3)과 독립적 — 이 요청의 성패가 다른 기능에 영향을 주지 않는다.

2.5 POST /api/search/rank-candidates

기능

Backend가 필터링한 후보 물건 목록과 취향 신호를 받아 LLM으로 의미 기반 순위를 매겨 반환한다.

Request

필드 타입 필수 설명 출처
candidates array O 후보 물건 목록 (item_id, title, description) Backend 필터링 결과
preference_text string - 요청자가 직접 입력한 취향 텍스트 Frontend 요청에서 전달
wishlist_titles array - 찜한 물건 제목 목록 Backend DB 조회
recent_search_keywords array - 최근 검색 키워드 (최대 10개) Backend DB 조회
requested_item_titles array - 요청 이력 (최신순) Backend DB 조회
completed_trade_titles array - 거래완료 이력 (최신순) Backend DB 조회

preference_text 이하 5개는 전부 선택이지만, 이 엔드포인트가 호출된다는 것 자체가 "최소 하나는 값이 있다"는 뜻이다.

요청 예시

{
  "candidates": [
    { "item_id": "item_101", "title": "다우니 섬유유연제 4L 새 제품 여분", "description": "대용량으로 구매했는데 2병이 생겨서 1병 교환합니다. 미개봉 새 제품" },
    { "item_id": "item_102", "title": "질레트 퓨전 면도날 8입 중 여분 4개", "description": "새 제품 미개봉, 다른 브랜드로 바꾸면서 여분이 생겼어요" },
    { "item_id": "item_103", "title": "동서 맥심 스틱커피 100개입 중 여분 30개", "description": "새 제품, 다 못 마실 것 같아 나눔합니다" }
  ],
  "preference_text": "세제나 생필품 여분 있으면 좋아요",
  "wishlist_titles": ["질레트 퓨전 면도날 새 제품 여분"],
  "recent_search_keywords": ["섬유유연제", "생필품 나눔"],
  "requested_item_titles": [],
  "completed_trade_titles": ["다우니 섬유유연제 4L 새 제품 여분"]
}

Response

필드 타입 필수 설명
results array O 순위 매겨진 item_id 배열 (3~5개)

응답 예시

{ "results": ["item_101", "item_102"] }

내부 처리

claude-sonnet-5. 후보 물건의 title/description을 취향 신호와 의미적으로 비교해 3~5개를 순위 순으로 선택하도록 지시. completed_trade_titles(실거래 완료)를 requested_item_titles(요청만)보다 강한 신호로, 최신 신호에 더 무게를 두도록 지시.

에러 케이스

상태 코드 상황 응답 예시
400 candidates가 비어있음 {"error": "invalid_input"}
500 Claude API 오류(요청 한도 초과, 응답 지연, 파싱 실패 등) {"error": "internal_error"}

실패 시 처리 정책

  • Claude API 실패(500)는 기술적 오류이므로 fallback 없이 그대로 에러 처리.
  • candidates가 비어있는 400 호출은 정상 흐름에선 발생하지 않아야 하며, 방어적으로만 처리.
  • results: []는 오류가 아니라 정상 응답(취향에 맞는 후보 없음)으로 간주.
  • 취향 신호가 전부 없으면 이 엔드포인트는 아예 호출되지 않는다.

2.6 POST /api/chat/draft-opening-message

기능

교환 요청 정보와 양측 거래·대화스타일 프로필을 반영해 첫 메시지 초안 텍스트를 생성한다.

Request

필드 타입 필수 설명
chat_room_id string O 채팅방 식별자. Backend가 양측 프로필을 내부 조회
requested_item.item_id string O 요청 대상 물건 ID
requested_item.quantity number O 요청 수량
offered_items[].item_id string O 제안한 물건 ID (1개 이상)
offered_items[].quantity number O 제안 물건별 수량

요청 예시

{
  "chat_room_id": "room_20260903_01",
  "requested_item": { "item_id": "item_456", "quantity": 1 },
  "offered_items": [{ "item_id": "item_123", "quantity": 1 }]
}

Response

필드 타입 필수 설명
draft_message string O 입력창에 채워질 초안 메시지 텍스트

응답 예시

{ "draft_message": "안녕하세요! 섬유유연제랑 햇반 교환 문의드려요. 혹시 교환 가능하실까요?" }

내부 처리

claude-haiku-5 (텍스트만 다루는 단순 조합형 생성). 요청/제안 물건 정보와 상대방 프로필 3항목(말투/대화 시작/설명 스타일)을 반영해 첫 인사 메시지 초안 1개 작성. 가격·교환비율 언급 금지, 상대 프로필 없으면 내 프로필로 폴백.

에러 케이스

상태 코드 상황 응답 예시
400 chat_room_id / requested_item / offered_items 중 누락 {"error": "invalid_input"}
500 Claude API 오류(요청 한도 초과, 응답 지연, 파싱 실패 등) {"error": "internal_error"}

실패 시 처리 정책

  • 500으로 실패해도 채팅방 개설 자체는 막지 않는다. 입력창을 빈 상태로 두고 사용자가 직접 작성하게 한다.
  • 보조 기능이므로 실패 영향 범위를 초안 텍스트 부재로 한정한다 (2.3처럼 등록을 차단하지 않음).
  • 프로필은 회원가입 시 필수 입력이라 항상 존재한다고 가정, "프로필 없음" 에러는 두지 않는다.

2.7 POST /api/chat/refine-message

기능

사용자가 입력한 메시지의 내용은 유지한 채 표현을 상대방 톤에 맞게 다듬는다.

Request

필드 타입 필수 설명
chat_room_id string O 채팅방 식별자. Backend가 양측 프로필을 내부 조회
original_text string O (공백/빈 문자열 불가) 사용자가 작성한 원본 메시지

요청 예시

{
  "original_text": "그건 좀 아닌 것 같은데요 다른 걸로 주세요",
  "my_profile": {
    "tone": "정중하게",
    "conversation_start": "인사 후 본론",
    "explanation_style": "적당히 설명",
    "proposal_style": "먼저 제안",
    "condition_flexibility": "적당히 조율",
    "opinion_expression": "분명하게",
    "schedule_coordination": "먼저 시간 제안"
  },
  "opponent_profile": {
    "tone": "친근하게",
    "conversation_start": "가볍게 대화부터",
    "explanation_style": "자세히 설명",
    "proposal_style": "상대 의견 먼저",
    "condition_flexibility": "유연하게 조율",
    "opinion_expression": "부드럽게",
    "schedule_coordination": "상대 시간에 맞춤"
  }
}

Response

필드 타입 필수 설명
refined_text string O 다듬어진 메시지 텍스트 (미리보기용, 자동 적용 아님)

응답 예시

{ "refined_text": "말씀 주신 조건은 조금 어려울 것 같아요. 혹시 다른 물건으로 조율해볼 수 있을까요?" }

내부 처리

claude-haiku-5. 원문의 사실관계(가격/수량/조건/날짜)는 절대 유지한 채, 표현만 항상 존댓말(프로필 무관)로 다듬고 이모지는 제거. 상대방 프로필 3항목(말투/설명 스타일/의견 표현)을 반영.

에러 케이스

상태 코드 상황 응답 예시
400 original_text가 비어있음 또는 chat_room_id 없음 {"error": "invalid_input"}
500 Claude API 오류(요청 한도 초과, 응답 지연, 파싱 실패 등) {"error": "internal_error"}

실패 시 처리 정책

  • 500 실패 시 원본 텍스트를 입력창에 그대로 유지, 사용자가 원문 전송 또는 직접 수정 가능.
  • "ㅇㅇ", "네" 같은 짧은 캐주얼 표현은 에러 처리하지 않고 존댓말 톤으로만 살짝 다듬어 반환될 수 있음.
  • 참여자 검증은 Backend가 미리 끝내므로, 이 API는 소유권을 재확인하지 않는다.

3. 서비스 구조 및 연동 관계

API가 전체 서비스에서 담당하는 역할과 연동 구조를 설명한다.

전체 구조

사용자
 ↓
Frontend
 ↓
Backend
 ↓
AI API Server (FastAPI)
 ↓
Claude API (모델) / 서버 알고리즘
 ↓
Backend
 ↓
Frontend

API 역할

  • Backend는 사용자 인증·리소스 소유권 검증을 마친 뒤, 필요한 데이터를 AI API로 전달한다.
  • AI API는 Claude 모델 호출(및 일부는 서버 알고리즘 후처리)로 결과를 생성한다.
  • AI API는 Claude의 원본 응답을 그대로 넘기지 않고, 외부 API 응답(200/4xx/5xx)으로 매핑하는 계층 역할을 겸한다.
  • 생성된 결과를 Backend에 반환하여 서비스 기능에 활용한다.

아래는 엔드포인트별로 위 공통 구조와 달라지는 지점(동기/비동기 여부, 다른 기능과의 의존 관계, 서버 알고리즘 개입 등)을 정리한 것이다.

3.1 /api/vision/analyze-item

AI 서버 (FastAPI)
POST /api/vision/analyze-item 요청 수신
        │
        ▼
Claude Vision API (claude-sonnet-5)
검열 + 제품 식별 + 등록용 초안 작성 (JSON 응답)
        │
        ▼
AI 서버 (FastAPI): 응답 매핑 처리
        │
   ┌────┴────┐
   ▼         ▼
검열 실패    검열 통과
(422)       (200)
   │         │
   ▼    ┌────┴────┐
사진     ▼         ▼
재업로드  confidence   confidence
안내    ≥ 0.25       < 0.25
        │            │
        ▼            ▼
     title/content   title/content = null
     그대로 반환      직접 입력 안내

검열(422)은 1~3장을 모두 함께 보고 판단하며, 세트 상품·클로즈업 샷은 정상으로 간주한다. "물건은 맞는데 흐릿하거나 애매한 사진"은 검열 대상이 아니다 — 통과(true)하되 confidence만 낮게 매겨진다.

3.2 /api/moderation/check-text

AI 서버 (FastAPI)
POST /api/moderation/check-text 요청 수신
        │
        ▼
Claude API (claude-haiku-5)
제목·내용 검열 (JSON 응답)
        │
        ▼
AI 서버 (FastAPI): 응답 매핑 처리
        │
   ┌────┴────┐
   ▼         ▼
검열 실패    검열 통과
(422)       (200, 본문 없음)
   │         │
   ▼         ▼
텍스트 수정   다음 단계(가치 평가) 진행
안내 반환    (등록·DB 저장은 이 API 범위 밖)

"서로 무관한 여러 물건 혼재 금지" 기준은 3.1(사진 검열)에도 동일하게 적용된다. 통과 여부만 반환할 뿐, 실제 DB 저장이나 다음 단계 호출을 직접 수행하지 않는다.

3.3 /api/value/estimate-price

AI 서버 (FastAPI)
POST /api/value/estimate-price 요청 수신
        │
        ▼
Claude API (claude-sonnet-5, web_search 활성화)
쇼핑몰 가격 검색 + 산정 (JSON 응답: prices, source, confidence)
        │
        ▼
AI 서버 (FastAPI): 알고리즘 처리
(이상치 제거 → 평균=referenceUnitPrice → 할인율 계산 → minUnitPrice)
        │
        ▼
AI 서버 (FastAPI): 응답 매핑 처리
        │
   ┌────┴────┐
   ▼         ▼
처리 실패    처리 성공
(500)       (200)
   │         │
   ▼         ▼
등록 차단,   referenceUnitPrice / minUnitPrice /
재시도 안내  maxUnitPrice / priceSource / confidence 반환
            (사용자에게 노출되지 않음, 매칭에만 사용)

3.2(텍스트 검열) 통과 직후, 등록하기 흐름 안에서 동기로 실행된다. prices 배열의 이상치 제거→평균→할인율 계산은 LLM이 아니라 서버 코드가 수행한다. maxUnitPrice는 항상 referenceUnitPrice와 동일하다.

3.4 /api/chatbot/ask

AI 서버 (FastAPI)
POST /api/chatbot/ask 요청 수신
        │
        ▼
메시지 임베딩 → 13개 정책 섹션과 유사도 비교 → 상위 1~2개 추출
        │
        ▼
Claude API (claude-sonnet-5)
정책 답변 생성 또는 화면 이동 판단 (JSON 응답: answer, action)
        │
        ▼
AI 서버 (FastAPI): 응답 파싱 및 반환
        │
        ▼
[프론트] answer 표시 + action 있으면 버튼 렌더링, 클릭 시 이동

정책 문서 13개 섹션은 사전에 임베딩되어 저장되어 있으며, 매 요청마다 새로 계산하지 않는다. 3.1~3.3(물건 등록 흐름)과 달리 독립적인 기능이다.

3.5 /api/search/rank-candidates

[Frontend] 스마트 검색 실행
      │  group_id 함께 전달
      ▼
[Backend] 본인 인증 + 모임 소속 확인
      │
      ▼
[Backend] 후보 필터링 (알고리즘)
      본인 물건 제외, 내 총 가치범위와 겹치는 후보만 추출
      후보 50개 초과 시 최신순으로 50개로 축소
      │
      ▼
[Backend] 취향 신호 일괄 조회 (DB)
      │
┄┄┄┄┄┄┄ 이 아래부터 AI 서버 영역 ┄┄┄┄┄┄┄
      │
신호가 하나라도 있음
      ▼
[AI 서버] POST /api/search/rank-candidates
      │
      ▼
Claude API (claude-sonnet-5)
후보 전체를 직접 읽고 취향 신호와 의미적으로 비교, 순위 매김
      │
      ▼
[AI 서버] 응답 매핑 → 성공(200) / 실패(500)
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
      │
신호 전혀 없음 → AI 서버 호출 없이 [Backend]가 가격 근접순 반환
      │
      ▼
[Backend] item_id로 상세정보 재조회 → [Frontend] 카드 표시

AI 서버는 이미 필터링·조회가 끝난 데이터를 받아 "순위를 매기는" 역할만 한다. 취향 신호가 전부 비어 있으면 AI 서버는 아예 호출되지 않는다.

3.6 /api/chat/draft-opening-message

[Frontend] 교환 요청 버튼 클릭 → 채팅방 개설과 동시에 호출
      │
      ▼
[Backend] 참여자 검증 + 교환 요청 정보/양측 프로필 조회
      ▼
[AI Server] → [Claude] 초안 생성
      │
      ▼
[Frontend] 입력창에 텍스트로 노출 (자동 전송 아님)

교환 요청 확정과 채팅방 개설, 초안 생성 호출은 하나의 흐름 안에서 이어지므로 그 사이 데이터 변경 가능성은 설계상 고려하지 않는다.

3.7 /api/chat/refine-message

[Frontend] 메시지 작성 완료 후 [AI 다듬기] 클릭
      │
      ▼
[Backend] 참여자 검증 + 양측 프로필 조회 (물건정보·대화내역 미조회)
      ▼
[AI Server] → [Claude] 톤 조정
      │
      ▼
[Frontend] 원본/다듬어진 버전 나란히 미리보기
      │
      ▼
[Frontend] [적용하기] → 입력창 텍스트 교체 (전송 아님)

[적용하기]는 입력창 텍스트 교체 동작일 뿐, 메시지 전송과는 분리된 별개의 사용자 행동이다.


4. API 호출 테스트

구현한 API를 실제로 호출하고 결과를 확인한다.

아래 요청/응답은 명세에 맞춰 구성한 예시값입니다. 실제 서버를 호출해 받은 응답이 아니므로, 제출 전 반드시 실제로 호출해서 이 예시를 실측 결과로 교체해야 합니다.

4.1 POST /api/vision/analyze-item

요청 예시 (multipart/form-data — JSON 아님, 파일 2장 첨부)

images: hetbahn_pack1.jpg, hetbahn_pack2.jpg

응답 예시

{
  "isAppropriate": true,
  "rejectionReason": null,
  "title": "햇반 210g 새 제품 4개 교환해요",
  "content": "햇반 210g 제품입니다. 24개입 세트로 구매했는데 다 못 먹을 것 같아서 4개만 교환하려고요. 미개봉 새 제품이고 유통기한은 2027년 6월까지예요.",
  "confidence": 0.97
}

응답 예시 (검열 실패, 422)

{
  "error": "unrelated_photo",
  "message": "상품과 무관한 사진이 섞여 있어요. 교환할 물건이 담긴 사진으로 다시 올려주세요"
}

4.2 POST /api/moderation/check-text

요청 예시

{
  "title": "햇반 210g 새 제품 4개 교환해요",
  "content": "24개입 세트로 구매했는데 다 못 먹을 것 같아서 4개만 교환하려고요. 미개봉 새 제품이고 유통기한은 2027년 6월까지예요."
}

응답 예시 (통과, 200)

본문 없음 — 상태 코드 200만 반환.

요청 예시 (차단 케이스)

{
  "title": "햇반 24개입 세트에서 4개 팔아요, 연락은 010-1234-5678로 주세요",
  "content": "미개봉 새 제품, 가격 협의 가능합니다."
}

응답 예시 (차단, 422)

{ "error": "personal_contact", "message": "개인 연락처는 채팅으로만 주고받을 수 있어요. 게시글에서 연락처를 제거해주세요." }

4.3 POST /api/value/estimate-price

요청 예시

{
  "title": "햇반 210g 새 제품 4개 교환해요",
  "content": "24개입 세트로 구매했는데 다 못 먹을 것 같아서 4개만 교환하려고요. 미개봉 새 제품이에요.",
  "valueTolerance": 0.6,
  "tradeSpeed": 0.4
}

응답 예시

{
  "referenceUnitPrice": 1300,
  "minUnitPrice": 1090,
  "maxUnitPrice": 1300,
  "priceSource": "new",
  "confidence": null
}

응답 예시 (검색 실패, estimated 전환)

{
  "referenceUnitPrice": 12000,
  "minUnitPrice": 10060,
  "maxUnitPrice": 12000,
  "priceSource": "estimated",
  "confidence": 0.4
}

4.4 POST /api/chatbot/ask

요청 예시

{
  "message": "탈퇴는 어떻게 해요?",
  "history": []
}

응답 예시

{
  "answer": "탈퇴는 마이페이지 > 계정 설정 > 탈퇴하기에서 진행하실 수 있어요. 탈퇴 후 7일 뒤 데이터가 완전히 삭제돼요.",
  "action": { "label": "마이페이지로 이동", "screen": "MY_PAGE" }
}

요청 예시 (정책 매칭 실패)

{
  "message": "환불 정책이 뭐예요?",
  "history": []
}

응답 예시

{
  "answer": "정확한 답변을 찾지 못했어요. 1:1 문의로 연결해드릴까요?",
  "action": { "label": "1:1 문의하기", "screen": "INQUIRY" }
}

4.5 POST /api/search/rank-candidates

요청 예시

{
  "candidates": [
    { "item_id": "item_101", "title": "다우니 섬유유연제 4L 새 제품 여분", "description": "대용량으로 구매했는데 2병이 생겨서 1병 교환합니다. 미개봉 새 제품" },
    { "item_id": "item_102", "title": "질레트 퓨전 면도날 8입 중 여분 4개", "description": "새 제품 미개봉, 다른 브랜드로 바꾸면서 여분이 생겼어요" },
    { "item_id": "item_103", "title": "동서 맥심 스틱커피 100개입 중 여분 30개", "description": "새 제품, 다 못 마실 것 같아 나눔합니다" }
  ],
  "preference_text": "세제나 생필품 여분 있으면 좋아요",
  "wishlist_titles": ["질레트 퓨전 면도날 새 제품 여분"],
  "recent_search_keywords": ["섬유유연제", "생필품 나눔"],
  "requested_item_titles": [],
  "completed_trade_titles": ["다우니 섬유유연제 4L 새 제품 여분"]
}

응답 예시

{ "results": ["item_101", "item_102"] }

4.6 POST /api/chat/draft-opening-message

요청 예시

{
  "chat_room_id": "room_20260903_01",
  "requested_item": { "item_id": "item_456", "quantity": 1 },
  "offered_items": [{ "item_id": "item_123", "quantity": 1 }]
}

응답 예시

{ "draft_message": "안녕하세요! 섬유유연제랑 햇반 교환 문의드려요. 혹시 교환 가능하실까요?" }

4.7 POST /api/chat/refine-message

요청 예시

{
  "chat_room_id": "room_20260903_01",
  "original_text": "그건 좀 아닌 것 같은데요 다른 걸로 주세요"
}

응답 예시

{ "refined_text": "말씀 주신 조건은 조금 어려울 것 같아요. 혹시 다른 물건으로 조율해볼 수 있을까요?" }
⚠️ **GitHub.com Fallback** ⚠️