# AI 토큰사용량 API 명세 > **작성일**: 2026-03-18 > **상태**: API 구현 완료, React 구현 대기 > **메뉴 위치**: 설정 > 토큰사용량 --- ## 1. 개요 테넌트 사용자가 자기 테넌트의 AI 사용량과 비용을 조회하는 API. MNG의 AI 토큰사용량과 동일한 역할이지만, 서비스 환경에 맞게 조정되었다. **MNG와의 차이:** - 테넌트 필터 없음 (인증된 사용자의 테넌트로 자동 필터) - 단가 설정은 읽기 전용 (MNG에서 관리) - 저장소 비용은 R2 기준 (GCS 아닌) --- ## 2. API 엔드포인트 ### 2.1 토큰 사용량 목록 + 통계 ``` GET /api/v1/settings/ai-token-usage ``` **Query Parameters:** | 파라미터 | 타입 | 필수 | 설명 | |---------|------|:----:|------| | `start_date` | string (YYYY-MM-DD) | - | 시작일 | | `end_date` | string (YYYY-MM-DD) | - | 종료일 (start_date 이후) | | `menu_name` | string | - | 호출 메뉴명 필터 (드롭다운 선택값) | | `per_page` | integer | - | 페이지당 건수 (기본 20, 범위 10~100) | | `page` | integer | - | 페이지 번호 (기본 1) | **Response:** ```json { "success": true, "data": { "items": { "data": [ { "id": 1, "model": "gemini-2.0-flash", "menu_name": "AI리포트-일간", "prompt_tokens": 2500, "completion_tokens": 800, "total_tokens": 3300, "cost_usd": "0.001320", "cost_krw": "1.85", "request_id": "a1b2c3d4-...", "created_by": 5, "creator": { "id": 5, "name": "홍길동" }, "created_at": "2026-03-18T10:30:00.000000Z", "updated_at": "2026-03-18T10:30:00.000000Z" } ], "current_page": 1, "last_page": 3, "per_page": 20, "total": 45, "from": 1, "to": 20 }, "stats": { "total_count": 45, "total_prompt_tokens": 125000, "total_completion_tokens": 38000, "total_total_tokens": 163000, "total_cost_usd": 0.0652, "total_cost_krw": 91.28 }, "menu_names": [ "AI리포트-일간", "AI리포트-주간", "AI리포트-월간", "명함OCR" ] } } ``` **핵심 포인트:** - `stats`: 현재 필터 조건의 전체 집계 (페이지네이션 무관) - `menu_names`: 해당 테넌트에서 사용한 고유 메뉴명 목록 (필터 드롭다운용) - `creator`: 사용자 정보 (nullable — 시스템 호출 시 null) --- ### 2.2 단가 설정 조회 ``` GET /api/v1/settings/ai-token-usage/pricing ``` **Response:** ```json { "success": true, "data": { "pricing": [ { "id": 1, "provider": "claude", "model_name": "claude-3-haiku", "input_price_per_million": "0.2500", "output_price_per_million": "1.2500", "unit_price": null, "unit_description": null, "exchange_rate": "1400.00", "is_active": true, "description": "Claude 3 Haiku" }, { "id": 5, "provider": "cloudflare-r2", "model_name": "cloud-storage", "input_price_per_million": "0.0000", "output_price_per_million": "0.0000", "unit_price": "0.004500", "unit_description": "per 1,000,000 Class A operations", "exchange_rate": "1400.00", "is_active": true, "description": "Cloudflare R2 Storage (S3 compatible)" }, { "id": 2, "provider": "gemini", "model_name": "gemini-2.0-flash", "input_price_per_million": "0.1000", "output_price_per_million": "0.4000", "unit_price": null, "unit_description": null, "exchange_rate": "1400.00", "is_active": true, "description": "Gemini 2.0 Flash" } ], "exchange_rate": 1400.0 } } ``` **pricing 레코드 구분:** - `input_price_per_million` / `output_price_per_million`: AI 토큰 기반 (gemini, claude) - `unit_price` / `unit_description`: 단위 기반 (R2 저장소, STT) - 두 유형이 동시에 null이 아닌 경우는 없음 --- ## 3. 화면 구성 가이드 ### 3.1 메뉴 위치 ``` 설정 (Settings) ├── ... └── 토큰사용량 (/settings/ai-token-usage) ``` ### 3.2 UI 구조 ``` ┌─────────────────────────────────────────────────────┐ │ AI 토큰 사용량 [단가 확인] │ ├─────────────────────────────────────────────────────┤ │ │ │ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ │ │ │총 호출수│ │입력토큰│ │출력토큰│ │비용 USD│ │비용 KRW│ │ │ │ 45건 │ │ 125K │ │ 38K │ │ $0.065│ │ 91원 │ │ │ └────────┘ └────────┘ └────────┘ └────────┘ └────────┘ │ │ │ │ 시작일[____] 종료일[____] 메뉴[▼____] [조회][초기화]│ │ │ │ ┌────┬──────┬──────┬──────┬──────┬───────┬──────┐ │ │ │No. │ 일시 │카테고리│ 메뉴 │ 모델 │ 토큰 │ 비용 │ │ │ ├────┼──────┼──────┼──────┼──────┼───────┼──────┤ │ │ │ 1 │03-18│AI리포트│ 일간 │gemini│ 3,300│$0.001│ │ │ │ 2 │03-17│명함OCR│ 인식 │gemini│ 1,200│$0.000│ │ │ └────┴──────┴──────┴──────┴──────┴───────┴──────┘ │ │ │ │ ◀ 1 2 3 ▶ 20건 / 총 45건 │ └─────────────────────────────────────────────────────┘ ``` ### 3.3 통계 카드 (5개) | 카드 | 데이터 소스 | 포맷 | |------|-----------|------| | 총 호출 수 | `stats.total_count` | `toLocaleString()` | | 입력 토큰 | `stats.total_prompt_tokens` | `toLocaleString()` | | 출력 토큰 | `stats.total_completion_tokens` | `toLocaleString()` | | 총 비용 (USD) | `stats.total_cost_usd` | `$0.0000` (소수점 4자리) | | 총 비용 (KRW) | `stats.total_cost_krw` | `0원` (정수 반올림) | ### 3.4 테이블 컬럼 | 컬럼 | 필드 | 정렬 | 비고 | |------|------|:----:|------| | No. | 자동 번호 | 중앙 | | | 사용일시 | `created_at` | 좌 | `YYYY-MM-DD HH:mm` 포맷 | | 카테고리 | `menu_name` 기반 | 좌 | 카테고리 매핑 함수 적용 | | 호출메뉴 | `menu_name` | 좌 | | | 모델 | `model` | 좌 | | | 입력토큰 | `prompt_tokens` | 우 | 천단위 콤마 | | 출력토큰 | `completion_tokens` | 우 | 천단위 콤마 | | 전체토큰 | `total_tokens` | 우 | 천단위 콤마 | | 비용(USD) | `cost_usd` | 우 | `$0.0000` | | 비용(KRW) | `cost_krw` | 우 | `0원` | | 사용자 | `creator.name` | 좌 | null이면 `-` 표시 | ### 3.5 카테고리 매핑 `menu_name` 필드를 기반으로 카테고리를 분류한다: ```typescript function getCategory(menuName: string): string { if (!menuName) return '-'; if (menuName.startsWith('AI리포트')) return 'AI리포트'; if (menuName.includes('명함')) return '명함OCR'; if (menuName.includes('사업자등록증')) return 'OCR'; if (menuName.startsWith('파일업로드')) return '파일저장소'; return menuName; } ``` ### 3.6 필터 - **서버 사이드 필터링** (클라이언트 필터링 아님) - 시작일 / 종료일: date picker - 메뉴: `menu_names` 배열로 드롭다운 구성 - 조회 버튼 클릭 시 API 재호출 - 초기화 버튼: 모든 필터 초기화 후 재호출 ### 3.7 단가 확인 모달 (읽기 전용) `[단가 확인]` 버튼 클릭 시 모달 표시. `GET /pricing` API 호출. **구성:** - AI 토큰 단가 테이블 (provider, model, 입력단가, 출력단가) - 저장소/서비스 단가 테이블 (provider, 단위가격, 단위설명) - 현재 환율 표시 - "단가 변경은 관리자에게 문의하세요" 안내 문구 --- ## 4. TypeScript 타입 정의 ```typescript interface AiTokenUsageItem { id: number; model: string; menu_name: string; prompt_tokens: number; completion_tokens: number; total_tokens: number; cost_usd: string; cost_krw: string; request_id: string; created_by: number | null; creator: { id: number; name: string } | null; created_at: string; } interface AiTokenUsageStats { total_count: number; total_prompt_tokens: number; total_completion_tokens: number; total_total_tokens: number; total_cost_usd: number; total_cost_krw: number; } interface AiTokenUsageListResponse { items: PaginatedResponse; stats: AiTokenUsageStats; menu_names: string[]; } interface AiPricingItem { id: number; provider: string; model_name: string; input_price_per_million: string | null; output_price_per_million: string | null; unit_price: string | null; unit_description: string | null; exchange_rate: string; is_active: boolean; description: string | null; } interface AiPricingResponse { pricing: AiPricingItem[]; exchange_rate: number; } ``` --- ## 5. 컴포넌트 구조 ``` src/app/[locale]/(protected)/settings/ai-token-usage/ └── page.tsx src/components/settings/AiTokenUsage/ ├── index.tsx ← 메인 (UniversalListPage 패턴) ├── actions.ts ← Server Actions ├── types.ts ← TypeScript 타입 ├── AiTokenUsageConfig.ts ← 카테고리 매핑, 포맷 유틸 └── PricingModal.tsx ← 단가 조회 모달 (읽기 전용) ``` --- ## 6. Server Actions ```typescript // actions.ts 'use server'; export async function getAiTokenUsageList(params?: { start_date?: string; end_date?: string; menu_name?: string; per_page?: number; page?: number; }): Promise> export async function getAiTokenUsagePricing(): Promise> ``` --- ## 관련 문서 - [features/ai/README.md](../../features/ai/README.md) — AI 기능 전체 개요 - [이관 기획서](../../plans/ai-token-usage-service-migration.md) — MNG→서비스 이관 상세 기획 --- **최종 업데이트**: 2026-03-18