# 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: ` 헤더 필수 (MVP) - 추후 OAuth/JWT 전환 예정 — 현재는 `.env` 의 `API_KEY` 값과 대조 - **콘텐츠 타입:** `application/json` - **시간:** 전부 ISO 8601 UTC (예: `2026-04-20T09:00:00Z`) - **UUID:** 모두 UUID v4 --- ## 1. `POST /api/clinics` — 병원 등록 ### Request ```json { "url": "https://www.banobagi.com", "name": "바노바기성형외과", "name_en": "BANOBAGI", "address": "서울시 강남구 논현로 842" } ``` ### Response `201 Created` ```json { "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 ```json { "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` ```json { "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 - `404` — `clinic_id` 존재 X - `422` — 채널 하나도 제공 안됨 --- ## 3. `GET /api/analyses/{run_id}/status` — 진행 상태 폴링 ### Response `200 OK` ```json { "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` 또는 `partial` → `GET /api/reports/{run_id}` 로 이동 - `error` → `channel_errors._pipeline` 에 원인 메시지 --- ## 4. `GET /api/reports/{run_id}` — 리포트 조회 ### Response `200 OK` `src/types/report.ts` 의 `MarketingReport` 타입과 100% 호환 JSON. 프런트는 이 응답을 `transformReport.ts` 에 전달. ```json { "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 ```json { "report_id": "22222222-...", "regenerate": false } ``` - `regenerate=false` (기본) — 기존 플랜 있으면 그대로 반환 - `regenerate=true` — 강제로 새 플랜 생성 ### Response `201 Created` (신규) or `200 OK` (기존) ```json { "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` ```json { "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 ```json { "youtube": "@banobagi", "instagram": ["@banobagi_official"] } ``` ### Response `200 OK` ```json { "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 (이미지 생성, 캡션 생성) - 성과 분석 / 스케줄링 자동 배포 - 팀/조직/권한 관리