# 이용현황(구독관리 통합) API 명세 > **작성일**: 2026-03-18 > **상태**: API 구현 완료, React 구현 대기 > **Base URL**: `api.codebridge-x.com` (운영) / `api.dev.codebridge-x.com` (개발) --- ## 1. 개요 기존 **구독관리**(`/subscription`)와 **이용현황**(`/usage`)을 하나의 페이지로 통합한다. 구독 정보, 리소스 사용량(사용자/저장공간), AI 토큰 사용량을 한 화면에 표시한다. ### 1.1 변경 요약 | 항목 | 변경 전 | 변경 후 | |------|--------|--------| | `/usage` | EmptyPage (미구현) | **통합 이용현황 페이지** | | `/subscription` | SubscriptionManagement | `/usage`로 리다이렉트 | | 메뉴 | 구독관리 + 이용현황 2개 | **이용현황** 1개로 통합 | | `api_calls` | 하드코딩 10,000 | **삭제** → `ai_tokens`로 대체 | | AI 토큰 | 없음 | **신규** (한도/비용/모델별) | | 저장공간 한도 | 10GB | **100GB** | ### 1.2 엔드포인트 요약 | # | Method | Path | 설명 | 변경 | |---|--------|------|------|------| | 1 | GET | `/api/v1/subscriptions/current` | 현재 활성 구독 | 기존 유지 | | 2 | GET | `/api/v1/subscriptions/usage` | **사용량 조회** | **응답 구조 변경** | | 3 | POST | `/api/v1/subscriptions/{id}/cancel` | 구독 취소 | 기존 유지 | | 4 | POST | `/api/v1/subscriptions/export` | 데이터 내보내기 | 기존 유지 | --- ## 2. API 상세 ### 2.1 사용량 조회 (변경됨) ``` GET /api/v1/subscriptions/usage ``` **Response:** ```json { "success": true, "data": { "users": { "used": 24, "limit": 10, "percentage": 240.0 }, "storage": { "used": 22808833, "used_formatted": "21.75 MB", "limit": 107374182400, "limit_formatted": "100 GB", "percentage": 0.0 }, "ai_tokens": { "period": "2026-03", "total_requests": 156, "total_tokens": 620000, "prompt_tokens": 412000, "completion_tokens": 208000, "limit": 1000000, "percentage": 62.0, "cost_usd": 0.95, "cost_krw": 1234, "warning_threshold": 80, "is_over_limit": false, "by_model": [ { "model": "gemini-2.0-flash", "requests": 120, "total_tokens": 496000, "cost_krw": 987 }, { "model": "claude-3-haiku", "requests": 36, "total_tokens": 124000, "cost_krw": 247 } ] }, "subscription": { "plan": "스탠다드", "monthly_fee": 79000, "status": "active", "started_at": "2026-01-01", "ended_at": "2026-12-31", "remaining_days": 288 } } } ``` **필드 설명:** | 섹션 | 필드 | 타입 | 설명 | |------|------|------|------| | `users` | `used` | number | 현재 사용자 수 | | | `limit` | number | 최대 사용자 수 (0이면 미설정) | | | `percentage` | number | 사용율 (%) | | `storage` | `used` | number | 사용 중인 용량 (bytes) | | | `used_formatted` | string | 읽기 쉬운 형식 ("21.75 MB") | | | `limit` | number | 저장소 한도 (bytes, 기본 100GB) | | | `limit_formatted` | string | 읽기 쉬운 형식 ("100 GB") | | | `percentage` | number | 사용율 (%) | | `ai_tokens` | `period` | string | 집계 기간 ("2026-03") | | | `total_requests` | number | 이번 달 총 AI 호출 수 | | | `total_tokens` | number | 이번 달 총 토큰 수 | | | `prompt_tokens` | number | 입력 토큰 합계 | | | `completion_tokens` | number | 출력 토큰 합계 | | | `limit` | number | 월별 토큰 한도 (기본 100만) | | | `percentage` | number | 한도 대비 사용율 (%) | | | `cost_usd` | number | 총 비용 (USD) | | | `cost_krw` | number | 총 비용 (원화) | | | `warning_threshold` | number | 경고 기준 (80%) | | | `is_over_limit` | boolean | 한도 초과 여부 | | | `by_model` | array | 모델별 사용량 내역 | | `by_model[]` | `model` | string | 모델명 ("gemini-2.0-flash") | | | `requests` | number | 호출 수 | | | `total_tokens` | number | 토큰 합계 | | | `cost_krw` | number | 비용 (원화) | | `subscription` | `plan` | string\|null | 요금제명 (null이면 미가입) | | | `monthly_fee` | number | 월 구독료 (원화) | | | `status` | string | 구독 상태 | | | `started_at` | string\|null | 시작일 | | | `ended_at` | string\|null | 종료일 | | | `remaining_days` | number\|null | 남은 일수 | **변경점 (기존 대비):** | 필드 | 변경 | |------|------| | `api_calls` | **삭제됨** | | `ai_tokens` | **신규** | | `subscription.plan` | Plan 테이블의 `name` (한글: "스탠다드" 등) | | `subscription.monthly_fee` | **신규** — Plan 테이블의 `price` (원화) | | `subscription.started_at` | 기존 `start_date` → 이름 변경 | | `subscription.ended_at` | 기존 `next_billing` → 이름 변경 | | `storage.limit` | 기본값 10GB → **100GB** | --- ## 3. TypeScript 타입 정의 ### 3.1 `UsageApiData` (변경) ```typescript export interface UsageApiData { users: { used: number; limit: number; percentage: number; }; storage: { used: number; used_formatted: string; limit: number; limit_formatted: string; percentage: number; }; ai_tokens: { period: string; total_requests: number; total_tokens: number; prompt_tokens: number; completion_tokens: number; limit: number; percentage: number; cost_usd: number; cost_krw: number; warning_threshold: number; is_over_limit: boolean; by_model: AiTokenByModel[]; }; subscription: { plan: string | null; monthly_fee: number; status: string; started_at: string | null; ended_at: string | null; remaining_days: number | null; }; } export interface AiTokenByModel { model: string; requests: number; total_tokens: number; cost_krw: number; } ``` ### 3.2 `SubscriptionInfo` (변경) ```typescript export interface SubscriptionInfo { id?: number; plan: string | null; planName: string; monthlyFee: number; status: SubscriptionStatus; startedAt: string | null; endedAt: string | null; remainingDays: number | null; userCount: number; userLimit: number; storageUsed: number; storageLimit: number; storageUsedFormatted: string; storageLimitFormatted: string; storagePercentage: number; aiTokens: { period: string; totalTokens: number; limit: number; percentage: number; costKrw: number; warningThreshold: number; isOverLimit: boolean; byModel: AiTokenByModel[]; }; } ``` --- ## 4. 화면 구성 ### 4.1 전체 레이아웃 ``` 이용현황 ┌─────────────────────────────────────────────────────────────┐ │ │ │ [섹션 1] 구독 정보 │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ 요금제 │ │ 구독 상태 │ │ 구독 금액 │ │ │ │ 스탠다드 │ │ ● 활성 │ │ ₩79,000/월 │ │ │ │ │ │ │ │ │ │ │ │ 시작: 01-01 │ │ 남은일: 288일│ │ 종료: 12-31 │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ │ [섹션 2] 리소스 사용량 │ │ ┌──────────────────────────────────────────────────┐ │ │ │ 사용자 │ │ │ │ ████████████████████████░░░░░░ 24 / 10명 │ │ │ │ │ │ │ │ 저장 공간 │ │ │ │ █░░░░░░░░░░░░░░░░░░░░░░░░░░░ 21.75 MB / 100 GB │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ [섹션 3] AI 토큰 사용량 — 2026년 3월 │ │ ┌──────────────────────────────────────────────────┐ │ │ │ ██████████████████░░░░░░░░░░░ 620,000 / 1,000,000│ │ │ │ 62.0% │ │ │ │ │ │ │ │ 총 비용: ₩1,234 │ │ │ │ │ │ │ │ 모델별 사용량: │ │ │ │ ┌────────────────────┬────────┬──────┬───────┐ │ │ │ │ │ 모델 │ 호출수 │ 토큰 │ 비용 │ │ │ │ │ ├────────────────────┼────────┼──────┼───────┤ │ │ │ │ │ gemini-2.0-flash │ 120 │ 496K │ ₩987 │ │ │ │ │ │ claude-3-haiku │ 36 │ 124K │ ₩247 │ │ │ │ │ └────────────────────┴────────┴──────┴───────┘ │ │ │ │ │ │ │ │ ※ 기본 제공: 월 100만 토큰. 초과 시 실비 과금 │ │ │ │ ※ 매월 1일 리셋, 잔여 토큰 이월 불가 │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ [섹션 4] 서비스 관리 │ │ ┌──────────────────────────────────────────────────┐ │ │ │ [ 자료 내보내기 ] [ 서비스 해지 ] │ │ │ └──────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ ``` ### 4.2 Progress Bar 색상 규칙 | 사용율 | 색상 | 비고 | |--------|------|------| | 0~60% | `bg-blue-500` | 정상 | | 60~80% | `bg-yellow-500` | 주의 | | 80~100% | `bg-orange-500` | 경고 (`warning_threshold` 기준) | | 100%+ | `bg-red-500` | 초과 (`is_over_limit: true`) | ### 4.3 AI 토큰 경고 표시 ```typescript // percentage >= warning_threshold (80%)이면 경고 배지 {aiTokens.percentage >= aiTokens.warningThreshold && ( 기본 제공량의 {aiTokens.percentage}% 사용 중 )} // is_over_limit이면 초과 배지 {aiTokens.isOverLimit && ( 한도 초과 — 초과분 실비 과금 )} ``` ### 4.4 구독 정보가 없는 경우 `subscription.plan`이 null이면: ``` 구독 정보가 없습니다. 관리자에게 문의하세요. ``` --- ## 5. 수정 대상 파일 ### 5.1 파일 목록 ``` src/app/[locale]/(protected)/ ├── usage/page.tsx ← 신규 └── subscription/page.tsx ← 수정: /usage로 리다이렉트 src/components/settings/SubscriptionManagement/ ├── types.ts ← 수정: api_calls 제거, ai_tokens 추가 ├── actions.ts ← 수정: buildApiUrl() 사용 ├── utils.ts ← 수정: transformApiToFrontend에 AI 토큰 매핑 ├── SubscriptionManagement.tsx ← 수정: AI 토큰 섹션 추가, toast 메시지 수정 ├── SubscriptionClient.tsx ← 수정: 통합 정리 (두 파일에 중복 로직 있음) └── index.ts ← 유지 ``` > **주의**: `SubscriptionManagement.tsx`와 `SubscriptionClient.tsx` 모두 `handleExportData`가 있고, 주석에 "SubscriptionManagement.tsx 사용 권장"이 있다. 통합 시 하나로 정리 권장. ### 5.2 Server Actions (`actions.ts`) 모든 URL을 `buildApiUrl()` 사용으로 변경한다. ```typescript 'use server'; import { buildApiUrl } from '@/lib/api/query-params'; import { executeServerAction, type ActionResult } from '@/lib/api/execute-server-action'; export async function getUsage(): Promise> { return executeServerAction({ url: buildApiUrl('/api/v1/subscriptions/usage'), errorMessage: '사용량 정보를 불러오는데 실패했습니다.', }); } export async function getCurrentSubscription(): Promise> { return executeServerAction({ url: buildApiUrl('/api/v1/subscriptions/current'), errorMessage: '구독 정보를 불러오는데 실패했습니다.', }); } export async function cancelSubscription(id: number, reason?: string): Promise { return executeServerAction({ url: buildApiUrl(`/api/v1/subscriptions/${id}/cancel`), method: 'POST', body: { reason }, errorMessage: '구독 취소에 실패했습니다.', }); } export async function requestDataExport(exportType: string = 'all'): Promise> { return executeServerAction({ url: buildApiUrl('/api/v1/subscriptions/export'), method: 'POST', body: { export_type: exportType }, errorMessage: '내보내기 요청에 실패했습니다.', }); } ``` ### 5.3 자료 내보내기 처리 주의사항 > `actions.ts`는 `'use server'` 파일이므로 `window.open()` 등 브라우저 API 사용 불가. > 이 프로젝트는 HttpOnly 쿠키 + Next.js API Proxy 패턴이므로 `Authorization: Bearer` 직접 전달도 불가. 내보내기 요청은 Server Action으로, 다운로드 URL 조립/열기는 클라이언트 컴포넌트에서 처리한다: ```typescript // 클라이언트 컴포넌트 내부 const handleExportData = async () => { const result = await requestDataExport('all'); if (result.success) { toast.success('자료 내보내기를 요청했습니다. 완료 시 알림으로 안내드립니다.'); } }; ``` ### 5.4 `/usage/page.tsx` (신규) ```typescript 'use client'; import { useEffect, useState } from 'react'; import { getSubscriptionData } from '@/components/settings/SubscriptionManagement/actions'; import type { SubscriptionInfo } from '@/components/settings/SubscriptionManagement/types'; import { SubscriptionManagement } from '@/components/settings/SubscriptionManagement'; export default function UsagePage() { const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { getSubscriptionData() .then(result => setData(result.data)) .finally(() => setIsLoading(false)); }, []); if (isLoading) return ; return ; } ``` ### 5.5 `/subscription/page.tsx` (리다이렉트) ```typescript 'use client'; import { useRouter } from 'next/navigation'; import { useEffect } from 'react'; export default function SubscriptionRedirect() { const router = useRouter(); useEffect(() => { router.replace('/usage'); }, [router]); return null; } ``` --- ## 6. 포맷팅 유틸 ```typescript export function formatTokenCount(tokens: number): string { if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}M`; if (tokens >= 1_000) return `${Math.round(tokens / 1_000).toLocaleString()}K`; return tokens.toLocaleString(); } export function formatKrw(amount: number): string { return `₩${Math.round(amount).toLocaleString()}`; } export function formatPeriod(period: string): string { const [year, month] = period.split('-'); return `${year}년 ${parseInt(month)}월`; } ``` --- ## 7. 제거 대상 | 파일 | 제거 대상 | 사유 | |------|----------|------| | `types.ts` | `api_calls` 인터페이스 | `ai_tokens`로 대체 | | `types.ts` | `apiCallsUsed`, `apiCallsLimit` | 제거 | | `SubscriptionManagement.tsx` | API 호출 수 Progress Bar | AI 토큰 Progress Bar로 대체 | | `SubscriptionManagement.tsx` | `'완료되면 알림을 보내드립니다.'` | `'자료 내보내기를 요청했습니다. 완료 시 알림으로 안내드립니다.'`로 변경 | | `SubscriptionClient.tsx` | `handleExportData` 중복 로직 | `SubscriptionManagement.tsx`로 통합 | | `utils.ts` | `apiCallsUsed/Limit` 변환 | `aiTokens` 변환으로 대체 | | `actions.ts` | `${API_URL}/...` 직접 조합 | `buildApiUrl()` 사용 | --- ## 8. 체크리스트 ### 타입/데이터 - [ ] `types.ts` — `api_calls` 제거, `ai_tokens` + `subscription` 변경 - [ ] `utils.ts` — `transformApiToFrontend()`에 AI 토큰 매핑 추가 - [ ] `actions.ts` — 모든 URL을 `buildApiUrl()` 사용 ### UI - [ ] `SubscriptionManagement.tsx` — AI 토큰 섹션 추가, API 호출 수 제거 - [ ] `SubscriptionManagement.tsx` — 내보내기 toast 메시지 수정 - [ ] `SubscriptionClient.tsx` — 중복 로직 정리 (SubscriptionManagement.tsx와 통합) - [ ] Progress Bar 색상 — 사용율별 (파랑→노랑→주황→빨강) - [ ] `by_model` 테이블 — 모델별 호출수/토큰/비용 - [ ] 한도 초과 경고 배지 — `is_over_limit`/`warning_threshold` - [ ] 구독 null 처리 (미가입 테넌트) - [ ] 로딩 스켈레톤 ### 페이지 - [ ] `/usage/page.tsx` 신규 생성 - [ ] `/subscription/page.tsx` → `/usage` 리다이렉트 ### 규칙 준수 - [ ] `buildApiUrl()` 필수 (`${API_URL}` 직접 조합 금지) - [ ] `'use server'` 파일에서 브라우저 API 사용 금지 - [ ] 다운로드는 API Proxy 경유 또는 서버 액션 처리 - [ ] `Authorization: Bearer` 직접 전달 금지 (HttpOnly 쿠키) --- **최종 업데이트**: 2026-03-18