Открытый API

Экспорт детализации использования

Руководство по экспорту использования LMU AI: войдите, чтобы получить JWT, затем вызовите /api/v1/usage с пагинацией, диапазоном дат и фильтрацией по модели, с примерами на трёх языках.

Войдите с помощью аккаунта и пароля, чтобы получить JWT, затем используйте этот JWT для вызова /api/v1/usage и получения полной таблицы детализации использования токенов, идентичной странице /usage в консоли.

Идеально для долго работающих скриптов, автоматизирующих экспорт, сверку и загрузку в корпоративный BI — не нужно каждый раз вручную выгружать CSV.

Почему нельзя получить это напрямую с помощью ключа API sk-...? Ключ предназначен только для бизнес-вызовов шлюза (/v1/messages и т. д.) и не открывает доступ к запросам детализации на уровне аккаунта — это намеренное решение по безопасности: если ключ утечёт, вы не захотите, чтобы весь ваш счёт стал доступен. Данные на уровне аккаунта должны использовать аутентификацию аккаунта (JWT).


1. Обзор API

ПараметрЗначение
Base URLhttps://api.lmuai.com
АутентификацияAuthorization: Bearer <access_token> (JWT, полученный при входе в аккаунт)
Срок действия access token86400 секунд (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-полеТипОписание
МодельmodelstringID модели, по которой фактически выставлен счёт
Усилие рассуждения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Все записи в кэш
Создание кэша 5мcache_creation_5m_tokensintЭфемерный уровень Anthropic на 5 минут
Создание кэша 1чcache_creation_1h_tokensintУровень Anthropic на 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Время записи на стороне сервера
ID запросаrequest_idstringДля устранения неполадок и трассировки
Генерация изображенийimage_count / image_size / image_output_tokens / image_output_costЗаполняется только для мультимодальных запросов
API Keyapi_key_id + вложенный объект api_keyint + obj
Группаgroup_id + вложенный объект groupint + obj

Каждая запись содержит вложенные объекты user / api_key / group (около +1КБ на строку). Объект api_key содержит полный текст ключа — обязательно скрывайте его в логах / скриншотах / CSV скриптов; для массового экспорта, если вложенные объекты вам не нужны, используйте jq или сопоставление полей на клиенте, чтобы оставить только нужные поля.

Перечисление типа биллинга (billing_type)

ЗначениеСмысл
0Оплата по токенам (подавляющее большинство запросов)
1Списание из квоты подписки

Поле billing_mode — строковая версия, описывающая конкретную модель ценообразования (token / per_request / image / subscription и т. д.); она более читаема и рекомендуется для категоризации.


4. Обновление токена

Access token истекает через 24 часа. Обновить его можно двумя способами:

Обновление с помощью refresh_token (рекомендуется для скриптов)

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

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

Возвращает новый access_token (и обычно также ротирует refresh_token). Refresh token действителен в течение 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 (включите её в настройках аккаунта). Этот 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
Запись кэша 5мcache_creation_5m_tokensint
Запись кэша 1чcache_creation_1h_tokensint
Стоимость входаinput_costUSD
Стоимость выходаoutput_costUSD
Стоимость создания кэшаcache_creation_costUSD
Стоимость чтения кэшаcache_read_costUSD
Итог по прайс-листуtotal_costUSD
Фактическое списаниеactual_costUSD
Первый токенfirst_token_msms
Общая длительностьduration_msms
Времяcreated_atISO8601
ID запросаrequest_idstring
Группаgroup_id / group.nameint / string
API Keyapi_key_id / api_key.nameint / string

Последнее обновление:

На этой странице