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

# Статистика

> Активность пользователей: генерации по категориям и расход токенов

Статистику вы забираете сами — мы не отправляем исходящих запросов к вашей системе. Данные доступны за любой период и обновляются по мере активности пользователей.

## Справочник событий

```http theme={null}
GET /api/v1/partner/stats/user-events
```

Возвращает список событий, по которым доступна статистика. Ключи из поля `type` — это же ключи объекта `stats` в ответах статистики.

```json theme={null}
[
  { "type": "generation.text",  "description": "Генераций текста" },
  { "type": "generation.image", "description": "Генераций изображений" },
  { "type": "generation.video", "description": "Генераций видео" },
  { "type": "generation.audio", "description": "Генераций аудио" },
  { "type": "tokens.spent",     "description": "Потрачено токенов" }
]
```

<ResponseField name="type" type="string">
  Ключ события. Используется как ключ в объекте `stats`.
</ResponseField>

<ResponseField name="description" type="string">
  Название события на русском языке — можно показывать в интерфейсе как есть.
</ResponseField>

<Note>
  Список пополняется. Стройте интерфейс по ответу этого эндпоинта, а не по фиксированному перечню ключей — тогда новые метрики появятся у вас без доработок.
</Note>

## Статистика пользователей

```http theme={null}
GET /api/v1/partner/stats/users
```

Постраничный список пользователей с их активностью. Это единственное место, где выдаётся `public_id` — сохраните его, чтобы потом запрашивать статистику по конкретному пользователю.

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

<ParamField query="cursor" type="integer">
  Позиция, с которой продолжить обход. Для первой страницы не передаётся, дальше — значение `next_cursor` из предыдущего ответа.
</ParamField>

<ParamField query="limit" default="20" type="integer">
  Размер страницы, от 1 до 100.
</ParamField>

<ParamField query="date_from" type="integer">
  Начало периода, unix-время. Не передан — считаем с начала истории.
</ParamField>

<ParamField query="date_to" type="integer">
  Конец периода, unix-время. Не передан — считаем по настоящий момент.
</ParamField>

### Ответ

```json theme={null}
{
  "items": [
    {
      "public_id": "9f1c2a6e-4b70-4f1a-9d2c-8b5e0a7d3f42",
      "external_user_id": "u_12345",
      "stats": {
        "generation.text": 128,
        "generation.image": 34,
        "generation.video": 6,
        "generation.audio": 2,
        "tokens.spent": 184300
      }
    }
  ],
  "next_cursor": 4820
}
```

<ResponseField name="items" type="array">
  Пользователи, у которых была активность за период.

  <Expandable title="поля">
    <ResponseField name="public_id" type="string">
      Идентификатор пользователя в Ai-Seven. Нужен для запроса статистики по одному пользователю.
    </ResponseField>

    <ResponseField name="external_user_id" type="string | null">
      Ваш идентификатор пользователя — тот, что вы передавали в `sub` токена входа. `null`, если пользователь пришёл не через вашу платформу.
    </ResponseField>

    <ResponseField name="stats" type="object">
      Суммы по каждому событию из справочника за период. События без активности присутствуют со значением `0`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="next_cursor" type="integer | null">
  Курсор следующей страницы. `null` — данные закончились.
</ResponseField>

## Статистика одного пользователя

```http theme={null}
GET /api/v1/partner/stats/users/{public_id}
```

<ParamField path="public_id" type="string" required>
  Идентификатор пользователя из списка выше.
</ParamField>

<ParamField query="date_from" type="integer">
  Начало периода, unix-время.
</ParamField>

<ParamField query="date_to" type="integer">
  Конец периода, unix-время.
</ParamField>

### Ответ

```json theme={null}
{
  "public_id": "9f1c2a6e-4b70-4f1a-9d2c-8b5e0a7d3f42",
  "external_user_id": "u_12345",
  "stats": {
    "generation.text": 12,
    "generation.image": 3,
    "generation.video": 0,
    "generation.audio": 0,
    "tokens.spent": 20450
  }
}
```

<ResponseField name="public_id" type="string">
  Идентификатор пользователя в Ai-Seven.
</ResponseField>

<ResponseField name="external_user_id" type="string | null">
  Ваш идентификатор пользователя.
</ResponseField>

<ResponseField name="stats" type="object">
  Суммы по каждому событию из справочника за период. События без активности присутствуют со значением `0`.
</ResponseField>

<Note>
  Ответ приходит и для пользователя без активности — со значениями `0`. Этим запрос по `public_id` отличается от списка, куда такой пользователь не попадёт вовсе.
</Note>

### Пример

Обход всех страниц за последние сутки:

<CodeGroup>
  ```python Python theme={null}
  import time
  import requests

  BASE = "https://aisevenai.ru/api/v1/partner"
  headers = {"Authorization": f"Bearer {api_token}"}
  params = {"date_from": int(time.time()) - 86400, "limit": 100}

  while True:
      response = requests.get(f"{BASE}/stats/users", headers=headers, params=params)
      response.raise_for_status()
      page = response.json()

      for user in page["items"]:
          print(user["external_user_id"], user["stats"])

      if not page["next_cursor"]:
          break
      params["cursor"] = page["next_cursor"]
  ```

  ```javascript Node.js theme={null}
  const BASE = "https://aisevenai.ru/api/v1/partner";
  const params = new URLSearchParams({
    date_from: String(Math.floor(Date.now() / 1000) - 86400),
    limit: "100",
  });

  for (;;) {
    const response = await fetch(`${BASE}/stats/users?${params}`, {
      headers: { Authorization: `Bearer ${apiToken}` },
    });
    const page = await response.json();

    for (const user of page.items) {
      console.log(user.external_user_id, user.stats);
    }

    if (!page.next_cursor) break;
    params.set("cursor", String(page.next_cursor));
  }
  ```

  ```bash cURL theme={null}
  curl -G https://aisevenai.ru/api/v1/partner/stats/users \
    -H "Authorization: Bearer $API_TOKEN" \
    -d date_from=$(( $(date +%s) - 86400 )) \
    -d limit=100
  ```
</CodeGroup>

## Поведение

<AccordionGroup>
  <Accordion title="Что попадает в статистику">
    Учитывается вся активность пользователя в Ai-Seven, а не только то, что он делал после перехода из вашего кабинета.

    Генерации считаются штуками и раскладываются по категориям: `text`, `image`, `video`, `audio`. Составные инструменты попадают в категорию своего результата — например, слайды карусели считаются изображениями, а озвучка — аудио.

    `tokens.spent` — сумма списанных токенов за период, включая операции, которые не являются генерацией.
  </Accordion>

  <Accordion title="Кто попадает в список">
    В список попадают пользователи, у которых **была активность за указанный период**. Пользователь без единого события за период не вернётся вовсе — это не то же самое, что нули в `stats`.

    Сам объект `stats` в обоих эндпоинтах устроен одинаково: в нём всегда присутствуют все события из справочника, отсутствующие — нулями.
  </Accordion>

  <Accordion title="Чужие пользователи в ответе">
    Список включает всех пользователей платформы, а не только пришедших через вашу интеграцию. У пользователей, пришедших не от вас, поле `external_user_id` равно `null`.

    Если вам нужны только свои — отбирайте по непустому `external_user_id` на своей стороне.
  </Accordion>

  <Accordion title="Границы периода">
    `date_from` и `date_to` — unix-время в секундах, обе границы включительно. Можно указать только одну: без `date_from` считаем с начала истории, без `date_to` — по текущий момент.

    События записываются в момент завершения операции. Генерация, начатая до `date_to` и завершившаяся после, попадёт в следующий период.
  </Accordion>
</AccordionGroup>

## Ошибки

Помимо [ошибок авторизации](/partner-api/overview#ошибки-авторизации):

| Код   | Сообщение              | Причина                                                                                                        |
| ----- | ---------------------- | -------------------------------------------------------------------------------------------------------------- |
| `404` | Пользователь не найден | Указан несуществующий `public_id`                                                                              |
| `422` | —                      | `public_id` не является UUID, `limit` вне диапазона 1–100, отрицательные даты или неизвестный параметр запроса |

<Tip>
  Для регулярной выгрузки берите период с запасом и опирайтесь на `date_from`/`date_to`, а не на «всё время»: так объём ответа остаётся предсказуемым, а повторный запрос за тот же период даёт тот же результат.
</Tip>
