o2o-infinith-backend/docs/API_CONTRACT.md

6.1 KiB

API_CONTRACT — MVP 엔드포인트 8개

단일 진실 소스. 프런트(infinith-web) ↔ 백엔드(infinith-api) 간 계약.

변경 절차:

  1. 이 문서 수정
  2. app/schemas/*.py + app/routers/*.py 구현 변경
  3. GET /openapi.json 결과를 PR 에 diff 로 첨부
  4. 프런트 npx openapi-typescript 로 타입 재생성
  5. 양쪽 PR 크로스 리뷰 후 머지

공통 사항

  • Base URL (dev): http://localhost:8000
  • Base URL (staging/prod): Railway 배포 후 확정
  • 인증: 모든 엔드포인트 X-API-Key: <secret> 헤더 필수 (MVP)
    • 추후 OAuth/JWT 전환 예정 — 현재는 .envAPI_KEY 값과 대조
  • 콘텐츠 타입: application/json
  • 시간: 전부 ISO 8601 UTC (예: 2026-04-20T09:00:00Z)
  • UUID: 모두 UUID v4

1. POST /api/clinics — 병원 등록

Request

{
  "url": "https://www.banobagi.com",
  "name": "바노바기성형외과",
  "name_en": "BANOBAGI",
  "address": "서울시 강남구 논현로 842"
}

Response 201 Created

{
  "id": "11111111-1111-1111-1111-111111111111",
  "url": "https://www.banobagi.com",
  "name": "바노바기성형외과",
  "created_at": "2026-04-20T09:00:00Z"
}

Error

  • 409 — 동일 URL 병원 이미 존재

2. POST /api/analyses — 분석 시작 (MVP 핵심)

Request

{
  "clinic_id": "11111111-1111-1111-1111-111111111111",
  "channels": {
    "youtube": "@banobagi",
    "instagram": ["@banobagi_official"],
    "facebook": "banobagiofficial",
    "naver_blog": "https://blog.naver.com/banobagi",
    "gangnam_unni": "https://www.gangnamunni.com/hospital/1234"
  },
  "options": {
    "skip_vision": false,
    "skip_perplexity": false
  }
}
  • clinic_id 대신 url 만 제공 시, 서버가 clinic_service.upsert_by_url() 로 자동 생성
  • 채널 필드는 전부 선택적 — 최소 1개 이상만 제공되면 OK
  • instagram 은 여러 계정 가능 (예: 본원 + 분원)

Response 202 Accepted

{
  "analysis_run_id": "22222222-2222-2222-2222-222222222222",
  "clinic_id": "11111111-1111-1111-1111-111111111111",
  "status": "discovering",
  "estimated_seconds": 90,
  "poll_url": "/api/analyses/22222222-2222-2222-2222-222222222222/status"
}

Error

  • 404clinic_id 존재 X
  • 422 — 채널 하나도 제공 안됨

3. GET /api/analyses/{run_id}/status — 진행 상태 폴링

Response 200 OK

{
  "analysis_run_id": "22222222-2222-2222-2222-222222222222",
  "status": "collecting",
  "progress": 0.45,
  "current_step": "채널 데이터 수집 중",
  "channel_errors": {
    "gangnam_unni": "로그인 벽으로 스크래핑 실패"
  },
  "completed_at": null
}

Status lifecycle

pending → discovering → collecting → generating → complete
                                                ↘ partial  (일부 채널 실패)
                                                ↘ error    (치명적 실패)

Frontend 폴링 가이드

  • status ∈ {complete, partial, error} 일 때까지 2초 간격 폴링
  • complete 또는 partialGET /api/reports/{run_id} 로 이동
  • errorchannel_errors._pipeline 에 원인 메시지

4. GET /api/reports/{run_id} — 리포트 조회

Response 200 OK

src/types/report.tsMarketingReport 타입과 100% 호환 JSON. 프런트는 이 응답을 transformReport.ts 에 전달.

{
  "id": "22222222-...",
  "clinic": {
    "name": "바노바기성형외과",
    "url": "https://www.banobagi.com"
  },
  "overall_score": 82,
  "youtube": { ... },
  "instagram": { ... },
  "facebook": { ... },
  "naver_place": { ... },
  "naver_blog": { ... },
  "gangnam_unni": { ... },
  "conversion_strategy": { ... },
  "roadmap": [ ... ],
  "kpis": [ ... ],
  "generated_at": "2026-04-20T09:01:30Z"
}

Error

  • 404 — run 존재 X
  • 409 — 아직 complete/partial 아님 (status: collecting 등)

5. POST /api/plans — 콘텐츠 기획 생성

Request

{
  "report_id": "22222222-...",
  "regenerate": false
}
  • regenerate=false (기본) — 기존 플랜 있으면 그대로 반환
  • regenerate=true — 강제로 새 플랜 생성

Response 201 Created (신규) or 200 OK (기존)

{
  "id": "33333333-...",
  "analysis_run_id": "22222222-...",
  "brand_guide": { ... },
  "channel_strategies": [ ... ],
  "content_strategy": { ... },
  "calendar": [ ... ],
  "created_at": "2026-04-20T09:10:00Z"
}

6. GET /api/plans/{id} — 기획 조회

MarketingPlan 타입 (src/types/plan.ts) 반환. 응답 구조는 5번과 동일.


7. GET /api/clinics/{id}/history — 분석 이력

Response 200 OK

{
  "clinic_id": "11111111-...",
  "runs": [
    {
      "run_id": "22222222-...",
      "status": "complete",
      "started_at": "2026-04-20T09:00:00Z",
      "completed_at": "2026-04-20T09:01:30Z",
      "overall_score": 82
    }
  ],
  "metrics_timeseries": {
    "youtube_subscribers": [
      {"date": "2026-04-20", "value": 12345},
      {"date": "2026-05-20", "value": 13200}
    ]
  }
}

8. POST /api/channels/verify — 핸들 실시간 검증

입력 폼의 "이 계정이 맞나요?" 버튼용.

Request

{
  "youtube": "@banobagi",
  "instagram": ["@banobagi_official"]
}

Response 200 OK

{
  "youtube": {
    "handle": "@banobagi",
    "verified": true,
    "display_name": "바노바기 BANOBAGI",
    "followers": 12345
  },
  "instagram": [
    {
      "handle": "@banobagi_official",
      "verified": "unverifiable",
      "note": "Instagram 로그인 벽"
    }
  ]
}
  • verified: true — 존재 확인
  • verified: false — 404/미존재
  • verified: "unverifiable" — 로그인 벽 등으로 확인 불가 (UI는 경고만, 진행 허용)

Out of scope (post-MVP)

  • 결제 / 구독
  • 경쟁사 분석 (자동 추천)
  • URL 자동 발견 (홈페이지 크롤링 기반)
  • 콘텐츠 스튜디오 API (이미지 생성, 캡션 생성)
  • 성과 분석 / 스케줄링 자동 배포
  • 팀/조직/권한 관리