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

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

URL: https://docs.lmuai.com/ru/docs/api/usage-export



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

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

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

***

## 1. Обзор API [#1-обзор-api]

| Параметр                    | Значение                                                                         |
| --------------------------- | -------------------------------------------------------------------------------- |
| Base URL                    | `https://api.lmuai.com`                                                          |
| Аутентификация              | `Authorization: Bearer <access_token>` (JWT, полученный при входе в аккаунт)     |
| Срок действия access token  | **86400 секунд (24 часа)**, обновляемый с помощью refresh\_token                 |
| Формат ответа               | JSON; `code: 0` означает успех, при неудаче `code != 0` с полем `message`        |
| Рекомендуемое использование | Автоматизированный экспорт биллинга, загрузка в корпоративный BI, скрипты сверки |

***

## 2. Шаг 1 — Войдите, чтобы получить JWT [#2-шаг-1--войдите-чтобы-получить-jwt]

### `POST /api/v1/auth/login` [#post-apiv1authlogin]

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

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

**Ответ** (фрагмент):

```json
{
  "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 — Получите детализацию использования [#3-шаг-2--получите-детализацию-использования]

### `GET /api/v1/usage` [#get-apiv1usage]

```http
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`                                                                                                                                         |

### Структура ответа [#структура-ответа]

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

<Callout type="warn">
  **Поля пагинации находятся на верхнем уровне `data`, а не во внешней обёртке**. Массив с детализацией находится в `data.items` — сам `data` не является массивом.
</Callout>

### Сопоставление полей для одной записи [#сопоставление-полей-для-одной-записи]

Они точно соответствуют каждому столбцу страницы `/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            | Все записи в кэш                                                                  |
| **Создание кэша 5м**        | `cache_creation_5m_tokens`                                                 | int            | Эфемерный уровень Anthropic на 5 минут                                            |
| **Создание кэша 1ч**        | `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      |                                                                                   |

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

### Перечисление типа биллинга (billing\_type) [#перечисление-типа-биллинга-billing_type]

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

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

***

## 4. Обновление токена [#4-обновление-токена]

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

### Обновление с помощью refresh\_token (рекомендуется для скриптов) [#обновление-с-помощью-refresh_token-рекомендуется-для-скриптов]

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

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

Возвращает новый `access_token` (и обычно также ротирует `refresh_token`). Refresh token действителен в течение 30 дней.

### Повторный вход [#повторный-вход]

Грубо, но просто, подходит для одноразовых скриптов: вызывайте `/auth/login` заново перед каждым запуском.

***

## 5. Примеры кода [#5-примеры-кода]

### Bash + curl + jq [#bash--curl--jq]

```bash
#!/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 [#python]

```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 [#nodejs]

```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. Практические заметки [#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. Рекомендации по безопасности (обязательно к прочтению) [#7-рекомендации-по-безопасности-обязательно-к-прочтению]

<Callout type="warn">
  **Ваш пароль от аккаунта + JWT эквивалентны полному доступу к аккаунту.** Перед скриптовым экспортом убедитесь, что:
</Callout>

| Параметр                    | Рекомендация                                                                                                                                                                                     |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Управление учётными данными | **Никогда** не коммитьте 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. Краткий справочник полей [#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          |
| Запись кэша 5м          | `cache_creation_5m_tokens`    | int          |
| Запись кэша 1ч          | `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 |
