# 외부 API 원가 **사이트 1건을 만드는 데 나가는 외부 API 비용**만 다룬다. 상한은 **$1(1,400원)** 이고, 문서가 아니라 코드가 강제한다(3절). 제품 제약으로서의 근거는 [PRODUCT.md 8절](PRODUCT.md). > ⚠️ 이 상한에 **개발비를 섞지 않는다.** 코드를 짜는 데 든 LLM 요금은 일회성 지출이고, > 사이트를 만들 때마다 반복해서 나가는 원가와는 성격이 다르다. --- ## 1. 공급자별 상태 (2026-08-31) | API | 용도 | 키 | 붙은 자리 | 과금 | |---|---|---|---|---| | **TourAPI** (`apis.data.go.kr`) | fact 주력 공급원 — 숙박·음식점 | ✅ | `collector/tour_api_adapter.py` | **무료** (개발계정 1,000건/일) | | **네이버 지역검색** (`openapi.naver.com`) | 동일 업소 검증 후보 | ✅ | `external/naver.py` | 무료 쿼터 | | **Perplexity Sonar** | 채널 URL 발견 | ✅ | `external/perplexity.py` | 요청 + 토큰 | | **Gemini** (AI Studio) | 사진 분류 · 소개문/FAQ · 붙여넣기 추출 | ✅ | `external/gemini.py` `gemini_text.py` `gemini_extract.py` | 토큰 | | **Kakao Local** (`dapi.kakao.com`) | 주변 정보 | ⬜ 미발급 | `external/kakao.py` | 건당 2원 | | **Open-Meteo** | 날씨 | 불필요 | `external/open_meteo.py` | 무료 | `.env` 의 키가 비어 있으면 **해당 어댑터만 비활성되고 서버는 그대로 뜬다** — 부팅이 외부 계약에 묶이면 안 되기 때문이다. 수집 어댑터는 `COLLECT_ADAPTERS` 로 배포 없이 개별로 끌 수 있다. 무엇을 어디서 가져오는지(필드 단위 카탈로그와 실측 충전율)는 [DATA_SOURCE_RESEARCH.md 8-3](DATA_SOURCE_RESEARCH.md). ## 2. 단가표 코드의 단일 출처는 `solution/backend/common/cost.py` 의 `RATES` 다. 아래는 그 요약이다. | API | 단가 | 확정 | |---|---|---| | Kakao Local | 키워드/카테고리 검색 **2원**, 좌표 변환 **0.5원** | ✅ `.env.example` | | Perplexity Sonar | 요청 7원 + 1K 토큰당 1.4원 (low context, USD_KRW 1,400) | ✅ 공식 단가표 | | Gemini | 이미지 1원 · 1K 토큰 0.5원 | ❌ **추정치** | | TourAPI · Open-Meteo | 0원 | ✅ | ★ **좌표 변환이 키워드 검색보다 4배 싸다.** 같은 공급자인데 단가가 다르므로 호출측이 `kakao_coord` 로 구분해 넘긴다. 지역 정보를 `place_id` 가 아니라 **행정구역 코드로 캐싱**하는 이유도 이것이다 — 같은 지역에 사이트 50개가 생겨도 외부 조회는 1회다. ⚠️ **Kakao 무료 쿼터 함정** — 무료 쿼터는 개발자 계정의 "첫 번째 활성 앱" 에만 붙는다. dev/stage/prod 앱을 따로 파면 **하나만 무료**다. 앱을 나누기 전에 확인할 것. ## 3. 상한을 코드가 강제한다 ```python meter = CostMeter(place_id=42) meter.guard(Provider.PERPLEXITY, searches=2, tokens=3000) # 호출 "전" → 넘으면 BudgetExceeded ...실제 호출... meter.charge(Provider.PERPLEXITY, searches=2, tokens=3100) # 호출 "후" 실측 기록 ``` - `guard()` 가 막으면 **그 호출을 하지 않는다.** 이미 쓴 돈은 못 돌려받지만 손실이 선형으로 늘어나는 걸 끊는다. - 예산 70% 소진 시 경고 로그, 100% 초과 시 예외. **미터 1개 = 사이트 1건.** - `solution/backend/common/cost.py` · 테스트 `tests/test_cost.py` 8건. ⚠️ **Gemini 단가가 아직 추정치다.** 그래서 `assert_rates_confirmed()` 가 실배치를 막는다 — 추정치 위에 "예산 안에 들어온다" 는 결론을 세우지 않기 위해서다. 공식 단가표로 `RATES` 를 교체하면서 `confirmed=True` 로 바꾼다. 비용이 실제로 붙는 순서: ``` 상호명 → [Perplexity: 채널 URL 발견] ← 요청 + 토큰 → [TourAPI: fact 수집] ← 무료 → 크롤링(무료) → [Gemini: 사진 분류 + 카피] ← 토큰 (사진 수에 비례) ``` **업소당 원가를 먼저 실측한 뒤 배치를 키우는 순서**가 안전하다. ## 4. 아직 없는 것 — 호출 로그 테이블 DB(`solution/backend/common/database/model/models.py`, 17테이블)에 **API 호출·비용을 적재하는 테이블이 없다.** 지금은 애플리케이션 로그로만 남는다(`perplexity.py` 가 토큰과 검색 횟수를 찍는 정도). 파이프라인을 배치로 돌리기 전에 `api_call_logs`(공급자 · 엔드포인트 · 토큰/호출수 · 추정단가 · `place_id` · 시각)를 얹어두면, 이 문서를 손으로 갱신하지 않고 쿼리로 뽑을 수 있다.