> ## Documentation Index
> Fetch the complete documentation index at: https://docs.apiyi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# API запроса логов

> Получайте логи вызовов программно, чтобы для каждого запроса получать модель, фактическую тарификацию, задержку и код ошибки, обеспечивая автоматическую сверку и самостоятельное устранение неполадок

## Обзор API

API журнала запросов возвращает подробную запись о **каждом API-вызове**, выполненном в вашей учетной записи,
включая использованную модель, фактически списанную сумму, задержку, был ли вызов выполнен с потоковой передачей,
а также код ошибки, если вызов завершается сбоем.

Он дополняет [API запроса баланса](/ru/api-capabilities/balance-query): запрос баланса показывает,
сколько кредита осталось, а запрос журнала показывает, куда он ушел.

Три типичных сценария использования:

<CardGroup cols={3}>
  <Card title="Автоматическая сверка" icon="calculator">
    Агрегируйте фактические расходы по временному диапазону или по модели и сверяйте их с вашей собственной тарификацией
  </Card>

  <Card title="Самостоятельное устранение неполадок" icon="bug">
    Анализируйте коды ошибок в неудачных запросах, чтобы отличать проблемы с параметрами от проблем на стороне upstream
  </Card>

  <Card title="Обращения в службу поддержки" icon="life-buoy">
    Предоставьте `request_id` службе поддержки, чтобы они могли точно определить конкретный вызов
  </Card>
</CardGroup>

<Info>
  Журналы также доступны для просмотра в консоли на странице журналов. Этот API — программная точка входа
  к тем же данным, предназначенная для автоматической сверки, плановых экспортов или передачи
  в вашу систему мониторинга. Для ручной проверки используйте консоль — см.
  [Как посмотреть мои записи вызовов](/ru/faq/call-logs).
</Info>

## Как получить ваш System Token

API для запросов журнала аутентифицируется с помощью **System Token**, который не является тем же самым, что и
API-ключ (см. Важные примечания в конце этой страницы).

<Steps>
  <Step title="Доступ к консоли">
    Перейдите `api.apiyi.com/account/profile`, чтобы открыть страницу профиля
  </Step>

  <Step title="Найдите System Token">
    Найдите раздел «Account Options - System Token» внизу страницы
  </Step>

  <Step title="Сгенерируйте AccessToken">
    Введите пароль своего аккаунта, чтобы получить AccessToken, который можно использовать для последующих API-запросов
  </Step>
</Steps>

<img src="https://mintcdn.com/apiyillc/PXVoab-l7wSQlQVE/images/apiyi-system-accesstoken.png?fit=max&auto=format&n=PXVoab-l7wSQlQVE&q=85&s=eb4f48476a795dfa5bfd7cb053081bdc" alt="Получить System Token" width="1020" height="460" data-path="images/apiyi-system-accesstoken.png" />

## Информация об API

| Элемент            | Описание                                                              |
| ------------------ | --------------------------------------------------------------------- |
| **API URL**        | `https://api.apiyi.com/api/log/self`                                  |
| **Метод**          | `GET`                                                                 |
| **Аутентификация** | заголовок Authorization (raw token string, **без префикса `Bearer`**) |
| **Формат ответа**  | JSON (сжатый gzip)                                                    |
| **Область данных** | Только журналы вашей учетной записи                                   |

## Детали запроса

### Заголовки запроса

| Имя заголовка   | Обязательно | Описание                                                    |
| --------------- | ----------- | ----------------------------------------------------------- |
| `Authorization` | Да          | Системный token, передается как необработанная строка token |
| `Accept`        | Нет         | Рекомендуется: `application/json`                           |

### Параметры запроса

| Параметр          | Тип     | Обязательно | Описание                                                               |
| ----------------- | ------- | ----------- | ---------------------------------------------------------------------- |
| `p`               | Integer | Нет         | Номер страницы, **с нулевой индексацией** (не 1)                       |
| `page_size`       | Integer | Нет         | Число записей на страницу, **ограничено 10** (см. предупреждение ниже) |
| `type`            | Integer | Нет         | Тип лога; передавайте `2` для сверки, см. таблицу ниже                 |
| `model_name`      | String  | Нет         | Фильтр точного совпадения по модели, например `gpt-5.6`                |
| `token_name`      | String  | Нет         | Фильтр по имени token                                                  |
| `start_timestamp` | Integer | Нет         | Время начала, Unix seconds                                             |
| `end_timestamp`   | Integer | Нет         | Время окончания, Unix seconds                                          |
| `group`           | String  | Нет         | Фильтр по группе                                                       |

<Warning>
  **Фактический лимит на `page_size` составляет 10.** Передача `page_size=100` все равно возвращает только 10 записей —
  это не ошибка, и это легко неверно прочитать как «я сделал только 10 вызовов за этот период».
  **Для любого значимого временного диапазона требуется пагинация**, с повторением цикла, пока ответ не вернет пустой
  массив. Примеры на Python и Node.js ниже уже это обрабатывают.
</Warning>

### Типы логов

| Значение | Смысл            | Примечания                                                                     |
| -------- | ---------------- | ------------------------------------------------------------------------------ |
| `1`      | Пополнение       | Записывает баланс до и после; `quota` равен 0                                  |
| `2`      | **Потребление**  | **Единственный тип, который нужен для сверки**; `quota` — фактическое списание |
| `3`      | Административный | Изменения учетной записи и подобные операции; `quota` равен 0                  |
| `4`      | Системный        | Кредит, предоставленный системой, и подобные операции; `quota` равен 0         |

<Warning>
  **Всегда передавайте `type=2` при расчете расходов.** Без него также возвращаются записи о пополнении и системном начислении.
  Их `quota` равен 0, но `model_name` и `token_name` тоже пусты, поэтому наивное
  суммирование или группировка по модели дадут неверные результаты.
</Warning>

## Детали ответа

### Пример успешного ответа

```json theme={null}
{
  "success": true,
  "message": "",
  "data": [
    {
      "request_id": "2026080114481936471351696e93ae3FTV8WKfk",
      "created_at": 1785595715,
      "type": 2,
      "content": "Fixed model price 0.015, group ratio 1",
      "username": "your-account",
      "token_name": "production-primary",
      "token_group": "default",
      "model_name": "gpt-5.6",
      "quota": 7500,
      "prompt_tokens": 1000,
      "completion_tokens": 0,
      "duration_for_view": 16,
      "is_stream": false,
      "error_code": "",
      "other": "{\"billing_type\":\"by_count\",\"request_path\":\"/v1/images/generations\",\"group_ratio\":1,\"model_ratio\":1,\"usage\":{}}"
    }
  ]
}
```

### Ключевые поля ответа

| Имя поля                              | Тип     | Описание                                                                                             |
| ------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------- |
| `quota`                               | Integer | **Фактически списанная за этот вызов сумма**, в credits; ÷ 500,000 = USD                             |
| `content`                             | String  | Понятная для человека заметка о тарификации, например фиксированная цена модели и коэффициент группы |
| `model_name`                          | String  | Модель, которая была фактически тарифицирована                                                       |
| `token_name`                          | String  | Какой API key сделал вызов                                                                           |
| `token_group`                         | String  | Группа token — обратите внимание, это идентификатор группы, см. FAQ                                  |
| `prompt_tokens` / `completion_tokens` | Integer | Количество входных / выходных token                                                                  |
| `duration_for_view`                   | Integer | Длительность вызова в секундах                                                                       |
| `is_stream`                           | Boolean | Был ли вызов выполнен с потоковой передачей                                                          |
| `error_code`                          | String  | Код причины сбоя; пустая строка при успехе                                                           |
| `created_at`                          | Integer | Время вызова, Unix seconds                                                                           |
| `request_id`                          | String  | **Request ID — укажите его при обращении в службу поддержки**                                        |
| `other`                               | String  | Дополнительные сведения о тарификации и запросе, **строка JSON, которую нужно распарсить еще раз**   |

<Info>
  Поле `other` содержит JSON **строку**, а не вложенный объект, поэтому требуется второй раз выполнить парсинг
  (`json.loads()` в Python, `JSON.parse()` в JavaScript). Оно содержит `billing_type`,
  `request_path` (фактически вызванный endpoint), `group_ratio`, `model_ratio` и `usage`.
</Info>

### Преобразование квоты

<Card title="Правило преобразования" icon="calculator">
  500,000 квота = \$1.00 USD
</Card>

**Формула:** сумма в USD = `quota` ÷ 500,000

**Примеры:**

* `quota: 7500` → \$0.015 USD
* `quota: 22500` → \$0.045 USD
* `quota: 18` → \$0.000036 USD

Это тот же пересчет, который используется в [API запроса баланса](/ru/api-capabilities/balance-query),
поэтому значения совпадают напрямую.

## Ответы с ошибкой

### HTTP 401 - Ошибка аутентификации

```json theme={null}
{
  "success": false,
  "message": "You are not authorized to perform this operation. The access token is invalid."
}
```

**Причина:** system token недействителен или срок его действия истек, либо API key (начинающийся с `sk-`) был
по ошибке использован как system token.

**Решение:** Сгенерируйте system token заново в консоли и убедитесь, что `Authorization` содержит
сырое значение **без префикса `Bearer`**.

## Примеры кода

### Пример cURL (одна страница, быстрая проверка)

```bash theme={null}
export APIYI_SYS_TOKEN='YOUR_SYSTEM_TOKEN'

curl --compressed -s 'https://api.apiyi.com/api/log/self?p=0&page_size=10&type=2' \
  -H "Authorization: $APIYI_SYS_TOKEN" \
  -H 'Accept: application/json' | jq '.data[] | {created_at, model_name, quota, request_id}'
```

<Warning>
  **Параметр `--compressed` обязателен**, потому что API возвращает сжатый gzip контент.
  Без него вы получите искаженный вывод.
</Warning>

<Info>
  Эта команда возвращает не более 10 записей. Для реальной сверки используйте приведенную ниже версию с пагинацией.
</Info>

### Пример Python (с пагинацией, готов к запуску)

```python theme={null}
import json
import os
import time
from collections import defaultdict

import requests

BASE = "https://api.apiyi.com"
TOKEN = os.environ["APIYI_SYS_TOKEN"]
QUOTA_PER_USD = 500_000

HEADERS = {"Authorization": TOKEN, "Accept": "application/json"}


def fetch_logs(hours=24, model_name=None):
    """Fetch consumption logs for the last N hours, paginating automatically."""
    now = int(time.time())
    params = {
        "page_size": 10,          # server-side cap is 10; larger values have no effect
        "type": 2,                # 2 = consumption, the only type used for reconciliation
        "start_timestamp": now - hours * 3600,
        "end_timestamp": now,
    }
    if model_name:
        params["model_name"] = model_name

    rows, seen = [], set()
    page = 0
    while True:
        resp = requests.get(f"{BASE}/api/log/self",
                            headers=HEADERS, params={**params, "p": page}, timeout=30)
        resp.raise_for_status()
        data = resp.json().get("data") or []
        if not data:
            break                 # an empty array means we reached the end

        fresh = 0
        for row in data:
            # some records always report id as 0, so deduplicate on request_id instead
            key = row.get("request_id")
            if key in seen:
                continue
            seen.add(key)
            rows.append(row)
            fresh += 1
        if fresh == 0:
            break                 # whole page was duplicates, defensive exit
        page += 1
    return rows


def summarize(rows):
    """Aggregate call count and spend per model."""
    stat = defaultdict(lambda: {"count": 0, "quota": 0})
    for row in rows:
        s = stat[row.get("model_name") or "(none)"]
        s["count"] += 1
        s["quota"] += row.get("quota") or 0

    total = sum(s["quota"] for s in stat.values())
    print(f"{'MODEL':32s} {'CALLS':>6s} {'SPEND(USD)':>12s}")
    for model, s in sorted(stat.items(), key=lambda kv: -kv[1]["quota"]):
        print(f"{model:32s} {s['count']:6d} {s['quota'] / QUOTA_PER_USD:12.4f}")
    print(f"\n{len(rows)} calls, {total:,} quota = ${total / QUOTA_PER_USD:.4f} USD")


if __name__ == "__main__":
    logs = fetch_logs(hours=24)
    summarize(logs)

    # other is a JSON string and needs a second parse
    if logs:
        extra = json.loads(logs[0].get("other") or "{}")
        print("\nEndpoint of the most recent call:", extra.get("request_path"))
```

**Пример вывода:**

```
MODEL                             CALLS   SPEND(USD)
gpt-5.6                             128       2.3850
gemini-3-pro-image                   30       1.3500
deepseek-chat                       412       0.0148

570 calls, 1,867,400 quota = $3.7348 USD
```

### Пример Node.js (с пагинацией)

```javascript theme={null}
const BASE = "https://api.apiyi.com";
const TOKEN = process.env.APIYI_SYS_TOKEN;
const QUOTA_PER_USD = 500_000;

async function fetchLogs({ hours = 24, modelName = null } = {}) {
  const now = Math.floor(Date.now() / 1000);
  const base = {
    page_size: "10",           // server-side cap is 10
    type: "2",                 // 2 = consumption
    start_timestamp: String(now - hours * 3600),
    end_timestamp: String(now),
  };
  if (modelName) base.model_name = modelName;

  const rows = [];
  const seen = new Set();
  for (let page = 0; ; page += 1) {
    const qs = new URLSearchParams({ ...base, p: String(page) });
    const resp = await fetch(`${BASE}/api/log/self?${qs}`, {
      headers: { Authorization: TOKEN, Accept: "application/json" },
    });
    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
    const { data } = await resp.json();
    if (!data || data.length === 0) break;

    let fresh = 0;
    for (const row of data) {
      // deduplicate on request_id, since some records always report id as 0
      if (seen.has(row.request_id)) continue;
      seen.add(row.request_id);
      rows.push(row);
      fresh += 1;
    }
    if (fresh === 0) break;
  }
  return rows;
}

const logs = await fetchLogs({ hours: 24 });
const total = logs.reduce((sum, r) => sum + (r.quota || 0), 0);
console.log(`${logs.length} calls, $${(total / QUOTA_PER_USD).toFixed(4)} USD`);
```

<Info>
  И библиотека Python requests, и fetch API Node.js автоматически распаковывают gzip,
  так что дополнительная настройка там не требуется. Только curl требует явный флаг `--compressed`.
</Info>

## Распространенные сценарии

### Расчет расходов за период времени

Используйте пример на Python выше. Два важных момента: передать `type=2` и разделить сумму `quota` на 500 000. Чтобы ограничить это одной моделью, добавьте параметр `model_name`.

### Поиск неудачных вызовов

```python theme={null}
failed = [r for r in fetch_logs(hours=24) if r.get("error_code")]
for r in failed:
    print(r["created_at"], r["model_name"], r["error_code"], r["request_id"])
```

<Info>
  Запросы, отклоненные шлюзом (недопустимые параметры и тому подобное), имеют `quota` равный 0 и
  **не тарифицируются**. `error_code` в журнале позволяет вам отличить «вызов завершился сбоем» от
  «вызов завершился успешно, но мне не понравился результат».
</Info>

### Предоставление идентификатора запроса для поддержки

Найдите проблемный вызов в журналах и передайте службе поддержки `request_id`. Это позволяет
однозначно идентифицировать точный запрос от начала до конца, что намного эффективнее, чем
описывать «вызов к какой-то модели завершился сбоем примерно в определенное время».

## Частые вопросы

<AccordionGroup>
  <Accordion title="Почему я получаю только 10 записей?">
    Серверный лимит на `page_size` равен 10. Более высокие значения не приводят к ошибке, но и не
    начинают действовать. Чтобы получить больше данных, нужно выполнять пагинацию
    (`p=0`, `p=1`, `p=2` и так далее), пока ответ не вернет пустой массив. Примеры на Python
    и Node.js выше уже это учитывают.
  </Accordion>

  <Accordion title="Некоторые поля в ответе пустые — это нормально?">
    Да. Некоторые поля содержат внутреннюю информацию платформы и с точки зрения обычной
    учетной записи пусты или равны нулю. Это ожидаемо и не влияет на поля, которые нужны вам для
    сверки или устранения неполадок — `quota`, `model_name`, `error_code` и `request_id`
    полностью заполнены.
  </Accordion>

  <Accordion title="Почему token_group отличается от названия группы, показанного в консоли?">
    API возвращает **идентификатор** группы, а консоль отображает **метку** группы.
    Они могут отличаться — например, API возвращает `default`, а консоль показывает Default.

    Полное сопоставление доступно в публичном эндпоинте `https://api.apiyi.com/api/pricing`
    в поле `usable_group`, которое сопоставляет идентификатор с меткой. Если вы хотите, чтобы ваши
    отчеты совпадали с консолью, примените это сопоставление самостоятельно.
  </Accordion>

  <Accordion title="Счетчики token и квота, похоже, не совпадают — чему верить?">
    Используйте `quota`. Это сумма, которая **фактически списывается** за вызов, и единственное
    поле, подходящее для сверки. Для моделей с оплатой за вызов, таких как генерация изображений
    и генерация видео, счетчики token в ответе могут быть значениями-заглушками, которые не
    участвуют в тарификации — такие модели возвращают `by_count` в `other.billing_type`.
  </Accordion>

  <Accordion title="Насколько далеко назад я могу делать запрос?">
    Укажите любой диапазон с помощью `start_timestamp` и `end_timestamp`. За точным сроком
    хранения исторических данных обратитесь в поддержку. Мы рекомендуем периодически
    экспортировать данные для сверки, а не полагаться на API для долгосрочного просмотра истории.
  </Accordion>

  <Accordion title="curl возвращает искаженный текст, или jq выдает ошибку">
    **Причина:** API возвращает сжатое gzip-содержимое (`Content-Encoding: gzip`), а curl
    не распаковывает его.

    **Решение:** добавьте флаг `--compressed`:

    ```bash theme={null}
    curl --compressed 'https://api.apiyi.com/api/log/self?p=0' \
      -H "Authorization: $APIYI_SYS_TOKEN" | jq
    ```

    Библиотека Python requests и Node.js fetch API распаковывают данные автоматически.
  </Accordion>

  <Accordion title="Запрос журналов расходует квоту?">
    Нет. Эндпоинт запроса журналов не расходует никакую квоту.
  </Accordion>
</AccordionGroup>

## Важные замечания

<Warning>
  **System token — это не API key, и они не взаимозаменяемы**

  * **API key** (начинается с `sk-`) предназначен для `/v1/*` inference-эндпоинтов. Если использовать его для
    `/api/log/self`, будет возвращён ответ 401.
  * **System token** (обычная строка без префикса) предназначен для `/api/*` management-эндпоинтов.
    Если использовать его для `/v1/chat/completions`, будет возвращена ошибка invalid-token.

  Область действия system token охватывает всю вашу учётную запись, поэтому **относитесь к нему как к паролю от вашей учётной записи**:
  храните его в secret manager, а не в коде, никогда не коммитьте его в репозиторий и периодически обновляйте его.
</Warning>

<Warning>
  **Ответы логов содержат ваши собственные API key в открытом виде**

  Каждая запись лога содержит информацию о token, который выполнил вызов. **Не вставляйте необработанные ответы логов в публичные места, не делитесь их скриншотами и не передавайте их третьим лицам** —
  перед экспортом удаляйте чувствительные поля.

  Обратите внимание, что в этом открытом тексте отсутствует префикс `sk-`, поэтому **обычные сканеры секретов могут его не обнаружить**. Не полагайтесь на автоматические проверки, чтобы они нашли его за вас.
</Warning>

<Info>
  **Лимиты запросов**

  * Оставляйте не менее 1 секунды между запросами, чтобы избежать лимита запросов
  * Установите разумный тайм-аут запроса (рекомендуется 30 секунд)
  * Для широких диапазонов времени реализуйте пагинацию и обработку повторных попыток
</Info>

<Card title="Связанная документация" icon="link">
  * [Balance Query API](/ru/api-capabilities/balance-query) — проверьте оставшийся кредит аккаунта
  * [Token Management API](/ru/api-capabilities/token-management) — создавайте и управляйте API key программно
  * [Как посмотреть мои записи вызовов](/ru/faq/call-logs) — ручной просмотр в консоли
  * [Как читать логи и тарификацию](/ru/faq/log-billing-explained) — как читать поля тарификации
</Card>
