Open API

사용 내역 내보내기

LMU AI 사용량 내보내기 가이드: 로그인하여 JWT를 발급받은 뒤 페이지네이션, 날짜 범위, 모델 필터링과 함께 /api/v1/usage를 호출하며, 세 가지 언어의 예제를 제공합니다.

계정과 비밀번호로 로그인하여 JWT를 발급받은 뒤, 그 JWT로 /api/v1/usage를 호출하면 콘솔 /usage 페이지와 동일한 전체 토큰 사용 내역 테이블을 가져올 수 있습니다.

내보내기, 대사(reconciliation), 엔터프라이즈 BI 적재를 자동화하는 장시간 실행 스크립트에 이상적이며, 매번 수동으로 CSV를 다운로드할 필요가 없습니다.

sk-... API 키로 직접 가져올 수 없나요? 이 키는 게이트웨이 비즈니스 호출(/v1/messages 등)에만 사용되며 계정 수준의 상세 조회는 노출하지 않습니다. 이는 의도적인 보안 설계입니다. 키가 유출되더라도 전체 청구 내역이 통째로 노출되는 것을 원치 않기 때문입니다. 계정 수준 데이터는 반드시 계정 인증(JWT)을 사용해야 합니다.


1. API 개요

항목
Base URLhttps://api.lmuai.com
인증Authorization: Bearer <access_token> (계정 로그인으로 발급받은 JWT)
액세스 토큰 유효 기간86400초 (24시간), refresh_token으로 갱신 가능
응답 형식JSON; code: 0은 성공, 실패 시 code != 0과 함께 message 필드
권장 용도자동화된 청구 내보내기, 엔터프라이즈 BI 적재, 대사 스크립트

2. 1단계 — 로그인하여 JWT 발급받기

POST /api/v1/auth/login

POST /api/v1/auth/login HTTP/1.1
Content-Type: application/json

{
  "email": "your@email.com",
  "password": "your-password"
}

응답 (발췌):

{
  "code": 0,
  "message": "success",
  "data": {
    "access_token": "eyJhbGc...",
    "refresh_token": "rt_756a220ffa...",
    "expires_in": 86400,
    "token_type": "Bearer",
    "user": { "id": 3, "email": "...", "balance": 1575.13 }
  }
}

이후의 모든 요청에는 Authorization: Bearer <access_token>을 포함하기만 하면 됩니다.


3. 2단계 — 사용 내역 가져오기

GET /api/v1/usage

GET /api/v1/usage?page=1&page_size=200&start_date=2026-06-01&end_date=2026-06-23&timezone=Asia/Shanghai
Authorization: Bearer <access_token>

쿼리 파라미터

파라미터타입필수기본값설명
pageint아니오1페이지 번호, 1부터 시작
page_sizeint아니오20페이지당 행 수, 200 권장 (과도한 값은 잘림)
start_datestring아니오시작 날짜 YYYY-MM-DD, timezone으로 파싱
end_datestring아니오종료 날짜 (해당 날짜 포함)
timezonestring아니오UTC날짜 파라미터를 파싱할 시간대, 예: Asia/Shanghai; 강력히 권장, 그렇지 않으면 경계가 로컬 시간과 일치하지 않음
api_key_idint아니오특정 키 하나의 내역만 조회 (현재 사용자에게 속해야 함)
modelstring아니오모델 ID로 필터링 (예: claude-sonnet-5)
streambool아니오true는 스트리밍만, false는 비스트리밍만
billing_typeint아니오청구 유형 열거값 (아래 참조)
sort_bystring아니오created_at정렬 필드
sort_orderstring아니오descasc 또는 desc

응답 구조

{
  "code": 0,
  "message": "success",
  "data": {
    "items": [ { /* UsageLog */ }, ... ],
    "total": 30423,
    "page": 1,
    "page_size": 200,
    "pages": 153
  }
}

페이지네이션 필드는 외부 엔벌로프가 아니라 data의 최상위 레벨에 있습니다. 상세 배열은 data.items에 있으며 — data 자체는 배열이 아닙니다.

단일 레코드의 필드 매핑

이들은 콘솔 /usage 페이지의 각 열과 정확히 대응됩니다:

콘솔 열JSON 필드타입설명
모델modelstring실제로 청구된 모델 ID
추론 강도reasoning_effortstring | nulllow / medium / high / xhigh / max
인바운드 엔드포인트inbound_endpointstring | null클라이언트가 호출한 경로, 예: /v1/messages
업스트림 엔드포인트upstream_endpointstring | null정규화된 업스트림 경로
요청 유형request_typestringchat / stream / responses / messages
스트리밍streambool스트리밍 여부
청구 모드billing_modestringtoken / per_request / image / subscription
청구 유형billing_typeint청구 유형 열거값
배수rate_multiplierfloat이 요청에 적용된 채널 배수
입력 토큰input_tokensint
출력 토큰output_tokensint
캐시 생성cache_creation_tokensint모든 캐시 쓰기
캐시 생성 5mcache_creation_5m_tokensintAnthropic 임시 5분 계층
캐시 생성 1hcache_creation_1h_tokensintAnthropic 1시간 계층
캐시 읽기cache_read_tokensint
입력 비용input_costfloatUSD
출력 비용output_costfloatUSD
캐시 생성 비용cache_creation_costfloatUSD
캐시 읽기 비용cache_read_costfloatUSD
정가 총액total_costfloatUSD, 배수 적용 전
실제 청구액actual_costfloat= total_cost × rate_multiplier, 잔액에서 실제로 차감되는 금액
첫 토큰 지연first_token_msint | nullTTFT, 스트리밍에서 유효
총 소요 시간duration_msint | null
시간created_atISO8601서버 측 기록 시간
요청 IDrequest_idstring문제 해결 및 추적용
이미지 생성image_count / image_size / image_output_tokens / image_output_cost멀티모달 요청에만 채워짐
API Keyapi_key_id + 중첩된 api_key 객체int + obj
그룹group_id + 중첩된 group 객체int + obj

각 레코드에는 중첩된 user / api_key / group 객체가 포함됩니다 (행당 약 +1KB). api_key 객체는 전체 키 텍스트를 포함하므로, 스크립트 로그 / 스크린샷 / CSV에서 반드시 마스킹하세요. 대량 내보내기 시 중첩 객체가 필요 없다면 jq나 클라이언트 측 필드 매핑을 사용해 필요한 필드만 유지하세요.

청구 유형 열거값 (billing_type)

의미
0토큰 기준 청구 (대다수 요청)
1구독 할당량에서 차감

billing_mode 필드는 문자열 버전으로, 구체적인 가격 모델(token / per_request / image / subscription 등)을 설명합니다. 더 읽기 쉬우므로 분류에는 이 필드를 사용하는 것이 권장됩니다.


4. 토큰 갱신

액세스 토큰은 24시간 후 만료됩니다. 갱신하는 방법은 두 가지입니다:

refresh_token으로 갱신 (스크립트에 권장)

POST /api/v1/auth/refresh
Content-Type: application/json

{
  "refresh_token": "rt_756a220ffa..."
}

access_token을 반환합니다 (보통 refresh_token도 함께 순환됩니다). refresh 토큰은 30일간 유효합니다.

다시 로그인

투박하지만 단순한 방법으로, 일회성 스크립트에 적합합니다. 매 실행 전에 /auth/login을 다시 호출합니다.


5. 코드 예제

Bash + curl + jq

#!/usr/bin/env bash
set -e

EMAIL="${EMAIL:?set EMAIL env}"
PASSWORD="${PASSWORD:?set PASSWORD env}"
BASE="https://api.lmuai.com"

JWT=$(curl -s -X POST "$BASE/api/v1/auth/login" \
  -H "Content-Type: application/json" \
  -d "{\"email\":\"$EMAIL\",\"password\":\"$PASSWORD\"}" \
  | jq -r '.data.access_token')

[ -n "$JWT" ] && [ "$JWT" != "null" ] || { echo "login failed" >&2; exit 1; }

START="2026-06-01"; END="2026-06-30"; TZ="Asia/Shanghai"; PAGE_SIZE=200

# Fetch page 1 to get total
META=$(curl -s "$BASE/api/v1/usage?page=1&page_size=$PAGE_SIZE&start_date=$START&end_date=$END&timezone=$TZ" \
  -H "Authorization: Bearer $JWT")
PAGES=$(echo "$META" | jq -r '.data.pages')

# CSV header
echo "created_at,model,request_type,input_tokens,output_tokens,cache_read_tokens,total_cost,actual_cost,duration_ms" > usage.csv

# Write page 1
echo "$META" | jq -r '.data.items[] | [.created_at, .model, .request_type, .input_tokens, .output_tokens, .cache_read_tokens, .total_cost, .actual_cost, .duration_ms] | @csv' >> usage.csv

# Fetch remaining pages
for ((p=2; p<=PAGES; p++)); do
  curl -s "$BASE/api/v1/usage?page=$p&page_size=$PAGE_SIZE&start_date=$START&end_date=$END&timezone=$TZ" \
    -H "Authorization: Bearer $JWT" \
    | jq -r '.data.items[] | [.created_at, .model, .request_type, .input_tokens, .output_tokens, .cache_read_tokens, .total_cost, .actual_cost, .duration_ms] | @csv' >> usage.csv
done

echo "saved $(wc -l < usage.csv) lines to usage.csv"

Python

import os, csv, requests

BASE = "https://api.lmuai.com"
EMAIL = os.environ["EMAIL"]
PASSWORD = os.environ["PASSWORD"]

def login() -> str:
    r = requests.post(f"{BASE}/api/v1/auth/login",
                      json={"email": EMAIL, "password": PASSWORD},
                      timeout=15)
    r.raise_for_status()
    data = r.json()
    if data["code"] != 0:
        raise RuntimeError(data.get("message", "login failed"))
    return data["data"]["access_token"]

def fetch_usage(jwt: str, **params):
    """Yield each row from paginated /usage endpoint."""
    page = 1
    while True:
        r = requests.get(f"{BASE}/api/v1/usage",
                         headers={"Authorization": f"Bearer {jwt}"},
                         params={**params, "page": page, "page_size": 200},
                         timeout=30)
        r.raise_for_status()
        d = r.json()["data"]
        for row in d.get("items", []):
            yield row
        if page >= d.get("pages", 1):
            return
        page += 1

if __name__ == "__main__":
    jwt = login()
    cols = ["created_at","model","request_type","input_tokens","output_tokens",
            "cache_read_tokens","total_cost","actual_cost","duration_ms"]
    with open("usage.csv", "w", newline="") as f:
        w = csv.writer(f); w.writerow(cols)
        for row in fetch_usage(jwt, start_date="2026-06-01", end_date="2026-06-30",
                               timezone="Asia/Shanghai"):
            w.writerow([row.get(c) for c in cols])
    print("done")

Node.js

import fs from 'node:fs'

const BASE = 'https://api.lmuai.com'
const { EMAIL, PASSWORD } = process.env

async function login() {
  const r = await fetch(`${BASE}/api/v1/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ email: EMAIL, password: PASSWORD }),
  })
  const j = await r.json()
  if (j.code !== 0) throw new Error(j.message || 'login failed')
  return j.data.access_token
}

async function* fetchUsage(jwt, params) {
  for (let page = 1; ; page++) {
    const q = new URLSearchParams({ ...params, page, page_size: '200' })
    const r = await fetch(`${BASE}/api/v1/usage?${q}`, {
      headers: { Authorization: `Bearer ${jwt}` },
    })
    const j = await r.json()
    for (const row of j.data.items) yield row
    if (page >= j.data.pages) return
  }
}

const jwt = await login()
const cols = ['created_at','model','request_type','input_tokens','output_tokens',
              'cache_read_tokens','total_cost','actual_cost','duration_ms']
const out = fs.createWriteStream('usage.csv')
out.write(cols.join(',') + '\n')
for await (const row of fetchUsage(jwt, {
  start_date: '2026-06-01', end_date: '2026-06-30', timezone: 'Asia/Shanghai',
})) {
  out.write(cols.map(c => JSON.stringify(row[c] ?? '')).join(',') + '\n')
}
console.log('done')

6. 실전 참고 사항

  1. 대량 페이지 넘김 사이에 지연을 추가하세요 — N개의 페이지를 연속으로 넘길 때는 속도 제한을 유발하지 않도록 sleep(0.1~0.3s)가 권장됩니다.
  2. 한 번에 너무 긴 시간 범위를 가져오지 마세요 — 여러 달에 걸쳐 수백만 행에 이르면 느립니다. 월별로 나누는 것이 더 안정적입니다.
  3. page_size는 200 권장 — 서버는 과도한 값을 잘라냅니다.
  4. 대역폭 절약 — 중첩된 user / api_key / group 객체는 크므로, 상세 행만 필요하다면 클라이언트에서 jq로 필터링하세요.
  5. 자주 발생하는 오류:
    • 401 JWT 만료 → refresh_token으로 갱신하거나 다시 로그인
    • 403 무권한 접근 (예: api_key_id로 다른 사람의 키를 조회) → 해당 키가 현재 사용자에게 속하는지 확인
    • 400 잘못된 파라미터 (예: start_date 형식) → YYYY-MM-DD + timezone이 유효한지 확인

7. 보안 권장 사항 (필독)

계정 비밀번호 + JWT는 전체 계정 접근 권한과 동일합니다. 내보내기를 스크립트로 작성하기 전에 반드시 확인하세요:

항목권장 사항
자격 증명 관리절대로 password / access_token / refresh_token을 git이나 CI 로그에 커밋하지 마세요. 환경 변수, 시크릿 매니저, 또는 .env (.gitignore와 함께)를 사용하세요
계정 보안TOTP 2단계 인증 활성화를 강력히 권장합니다 (계정 설정에서 활성화). 이 API는 로그인 시 TOTP 검증을 지원합니다
정기 순환계정 비밀번호를 정기적으로 변경하고, refresh_token 만료 후 즉시 순환하세요
이상 모니터링계정의 "마지막 로그인 시간 / IP"를 주시하세요. 낯선 기기가 보이면 즉시 비밀번호를 변경하세요
필드 마스킹응답의 중첩된 api_key.key (전체 sk-... 텍스트), user.email, refresh_token은 모두 민감 필드입니다 — 출력 / 로그 / CSV 전에 마스킹하세요
최소 권한분리할 수 있다면 내보내기 스크립트에 전용 하위 계정을 사용하세요 (조직이 다중 계정 구조를 가진 경우). 이는 메인 계정의 장기 노출을 피하기 위함입니다
오류 재시도로그인 실패 시 무한정 재시도하지 마세요 — 리스크 컨트롤을 쉽게 유발합니다. 지수 백오프가 권장됩니다

8. 필드 빠른 참조

CSV 열 매핑에 유용합니다:

레이블필드단위
모델model
추론 강도reasoning_effort
인바운드 엔드포인트inbound_endpointpath
업스트림 엔드포인트upstream_endpointpath
요청 유형request_typeenum
스트리밍streambool
청구 모드billing_modeenum
청구 유형billing_typeint
배수rate_multiplierfloat
입력 토큰input_tokensint
출력 토큰output_tokensint
캐시 읽기 토큰cache_read_tokensint
캐시 쓰기 토큰cache_creation_tokensint
캐시 쓰기 5mcache_creation_5m_tokensint
캐시 쓰기 1hcache_creation_1h_tokensint
입력 비용input_costUSD
출력 비용output_costUSD
캐시 생성 비용cache_creation_costUSD
캐시 읽기 비용cache_read_costUSD
정가 총액total_costUSD
실제 청구액actual_costUSD
첫 토큰first_token_msms
총 소요 시간duration_msms
시간created_atISO8601
요청 IDrequest_idstring
그룹group_id / group.nameint / string
API Keyapi_key_id / api_key.nameint / string

마지막 업데이트:

이 페이지의 목차