사용 내역 내보내기
LMU AI 사용량 내보내기 가이드: 로그인하여 JWT를 발급받은 뒤 페이지네이션, 날짜 범위, 모델 필터링과 함께 /api/v1/usage를 호출하며, 세 가지 언어의 예제를 제공합니다.
계정과 비밀번호로 로그인하여 JWT를 발급받은 뒤, 그 JWT로 /api/v1/usage를 호출하면 콘솔 /usage 페이지와 동일한 전체 토큰 사용 내역 테이블을 가져올 수 있습니다.
내보내기, 대사(reconciliation), 엔터프라이즈 BI 적재를 자동화하는 장시간 실행 스크립트에 이상적이며, 매번 수동으로 CSV를 다운로드할 필요가 없습니다.
왜 sk-... API 키로 직접 가져올 수 없나요? 이 키는 게이트웨이 비즈니스 호출(/v1/messages 등)에만 사용되며 계정 수준의 상세 조회는 노출하지 않습니다. 이는 의도적인 보안 설계입니다. 키가 유출되더라도 전체 청구 내역이 통째로 노출되는 것을 원치 않기 때문입니다. 계정 수준 데이터는 반드시 계정 인증(JWT)을 사용해야 합니다.
1. API 개요
| 항목 | 값 |
|---|---|
| Base URL | https://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>쿼리 파라미터
| 파라미터 | 타입 | 필수 | 기본값 | 설명 |
|---|---|---|---|---|
page | int | 아니오 | 1 | 페이지 번호, 1부터 시작 |
page_size | int | 아니오 | 20 | 페이지당 행 수, 200 권장 (과도한 값은 잘림) |
start_date | string | 아니오 | — | 시작 날짜 YYYY-MM-DD, timezone으로 파싱 |
end_date | string | 아니오 | — | 종료 날짜 (해당 날짜 포함) |
timezone | string | 아니오 | UTC | 날짜 파라미터를 파싱할 시간대, 예: Asia/Shanghai; 강력히 권장, 그렇지 않으면 경계가 로컬 시간과 일치하지 않음 |
api_key_id | int | 아니오 | — | 특정 키 하나의 내역만 조회 (현재 사용자에게 속해야 함) |
model | string | 아니오 | — | 모델 ID로 필터링 (예: claude-sonnet-5) |
stream | bool | 아니오 | — | true는 스트리밍만, false는 비스트리밍만 |
billing_type | int | 아니오 | — | 청구 유형 열거값 (아래 참조) |
sort_by | string | 아니오 | created_at | 정렬 필드 |
sort_order | string | 아니오 | desc | asc 또는 desc |
응답 구조
{
"code": 0,
"message": "success",
"data": {
"items": [ { /* UsageLog */ }, ... ],
"total": 30423,
"page": 1,
"page_size": 200,
"pages": 153
}
}페이지네이션 필드는 외부 엔벌로프가 아니라 data의 최상위 레벨에 있습니다. 상세 배열은 data.items에 있으며 — data 자체는 배열이 아닙니다.
단일 레코드의 필드 매핑
이들은 콘솔 /usage 페이지의 각 열과 정확히 대응됩니다:
| 콘솔 열 | JSON 필드 | 타입 | 설명 |
|---|---|---|---|
| 모델 | model | string | 실제로 청구된 모델 ID |
| 추론 강도 | reasoning_effort | string | null | low / medium / high / xhigh / max |
| 인바운드 엔드포인트 | inbound_endpoint | string | null | 클라이언트가 호출한 경로, 예: /v1/messages |
| 업스트림 엔드포인트 | upstream_endpoint | string | null | 정규화된 업스트림 경로 |
| 요청 유형 | request_type | string | chat / stream / responses / messages 등 |
| 스트리밍 | stream | bool | 스트리밍 여부 |
| 청구 모드 | billing_mode | string | token / per_request / image / subscription |
| 청구 유형 | billing_type | int | 청구 유형 열거값 |
| 배수 | rate_multiplier | float | 이 요청에 적용된 채널 배수 |
| 입력 토큰 | input_tokens | int | |
| 출력 토큰 | output_tokens | int | |
| 캐시 생성 | cache_creation_tokens | int | 모든 캐시 쓰기 |
| 캐시 생성 5m | cache_creation_5m_tokens | int | Anthropic 임시 5분 계층 |
| 캐시 생성 1h | cache_creation_1h_tokens | int | Anthropic 1시간 계층 |
| 캐시 읽기 | cache_read_tokens | int | |
| 입력 비용 | input_cost | float | USD |
| 출력 비용 | output_cost | float | USD |
| 캐시 생성 비용 | cache_creation_cost | float | USD |
| 캐시 읽기 비용 | cache_read_cost | float | USD |
| 정가 총액 | total_cost | float | USD, 배수 적용 전 |
| 실제 청구액 | actual_cost | float | = total_cost × rate_multiplier, 잔액에서 실제로 차감되는 금액 |
| 첫 토큰 지연 | first_token_ms | int | null | TTFT, 스트리밍에서 유효 |
| 총 소요 시간 | duration_ms | int | null | |
| 시간 | created_at | ISO8601 | 서버 측 기록 시간 |
| 요청 ID | request_id | string | 문제 해결 및 추적용 |
| 이미지 생성 | image_count / image_size / image_output_tokens / image_output_cost | — | 멀티모달 요청에만 채워짐 |
| API Key | api_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. 실전 참고 사항
- 대량 페이지 넘김 사이에 지연을 추가하세요 — N개의 페이지를 연속으로 넘길 때는 속도 제한을 유발하지 않도록
sleep(0.1~0.3s)가 권장됩니다. - 한 번에 너무 긴 시간 범위를 가져오지 마세요 — 여러 달에 걸쳐 수백만 행에 이르면 느립니다. 월별로 나누는 것이 더 안정적입니다.
page_size는 200 권장 — 서버는 과도한 값을 잘라냅니다.- 대역폭 절약 — 중첩된
user/api_key/group객체는 크므로, 상세 행만 필요하다면 클라이언트에서jq로 필터링하세요. - 자주 발생하는 오류:
401JWT 만료 →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_endpoint | path |
| 업스트림 엔드포인트 | upstream_endpoint | path |
| 요청 유형 | request_type | enum |
| 스트리밍 | stream | bool |
| 청구 모드 | billing_mode | enum |
| 청구 유형 | billing_type | int |
| 배수 | rate_multiplier | float |
| 입력 토큰 | input_tokens | int |
| 출력 토큰 | output_tokens | int |
| 캐시 읽기 토큰 | cache_read_tokens | int |
| 캐시 쓰기 토큰 | cache_creation_tokens | int |
| 캐시 쓰기 5m | cache_creation_5m_tokens | int |
| 캐시 쓰기 1h | cache_creation_1h_tokens | int |
| 입력 비용 | input_cost | USD |
| 출력 비용 | output_cost | USD |
| 캐시 생성 비용 | cache_creation_cost | USD |
| 캐시 읽기 비용 | cache_read_cost | USD |
| 정가 총액 | total_cost | USD |
| 실제 청구액 | actual_cost | USD |
| 첫 토큰 | first_token_ms | ms |
| 총 소요 시간 | duration_ms | ms |
| 시간 | created_at | ISO8601 |
| 요청 ID | request_id | string |
| 그룹 | group_id / group.name | int / string |
| API Key | api_key_id / api_key.name | int / string |
마지막 업데이트:
LMU AI API 키를 발급받고 Claude와 Codex를 바로 사용하세요
무료 가입, 유연한 요금제. 하나의 API 키로 Claude Code, Codex CLI, Cursor, VS Code 확장, OpenCode, Cherry Studio 등 AI 도구에 연결됩니다.
가입하기