Files
sam-docs/frontend/api-specs/ai-token-usage-api.md
김보곤 8cb8bd4c6c docs: [ai] AI 토큰사용량 API 문서화
- features/ai/README.md: 토큰사용량, 단가설정, R2 비용추적 내용 추가
- frontend/api-specs/ai-token-usage-api.md: React 프론트엔드 개발자용 API 명세 신규 작성
2026-03-18 09:49:40 +09:00

11 KiB

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:

{
  "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:

{
  "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 필드를 기반으로 카테고리를 분류한다:

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 타입 정의

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<AiTokenUsageItem>;
  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

// actions.ts
'use server';

export async function getAiTokenUsageList(params?: {
  start_date?: string;
  end_date?: string;
  menu_name?: string;
  per_page?: number;
  page?: number;
}): Promise<ActionResult<AiTokenUsageListResponse>>

export async function getAiTokenUsagePricing():
  Promise<ActionResult<AiPricingResponse>>

관련 문서


최종 업데이트: 2026-03-18