Пользователи вашего сервиса (user)

Передавайте в запросах идентификатор своего пользователя: по нему можно посчитать расход по клиентам, найти запрос при жалобе и выставить счёт.

Где передать
Поле user в теле любого платного метода; в /messages — metadata.user_id; в multipart-запросах (/audio/transcriptions, /images/edits) — поле формы user
Длина
До 128 символов, лишнее обрезается
Что передавать
Постоянный непрозрачный ID: UUID, номер из вашей базы, хеш. Не почту и не телефон

Поле необязательное и ничего не меняет в ответе модели и в цене.

Как передать

curl
curl https://api.aitunnel.ru/v1/chat/completions \
  -H "Authorization: Bearer sk-aitunnel-xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5-nano",
    "user": "customer-42",
    "messages": [{ "role": "user", "content": "Привет!" }]
  }'
Python
from openai import OpenAI

client = OpenAI(
    base_url="https://api.aitunnel.ru/v1",
    api_key="sk-aitunnel-xxx",
)

response = client.chat.completions.create(
    model="gpt-5-nano",
    messages=[{"role": "user", "content": "Привет!"}],
    user="customer-42",  # ваш идентификатор клиента
)

# Сохраните id рядом со своей записью: по нему потом можно разобрать запрос
print(response.id)  # chat-034pYx9KaOzvCxWdvYoAvp

Anthropic Messages принимает идентификатор в metadata.user_id:

Messages
curl https://api.aitunnel.ru/v1/messages \
  -H "Authorization: Bearer sk-aitunnel-xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-4.6",
    "max_tokens": 1024,
    "metadata": { "user_id": "customer-42" },
    "messages": [{ "role": "user", "content": "Привет!" }]
  }'

Остальные методы — так же, полем user рядом с model: /images/generations, /audio/speech, /rerank, /decisions, /videos, /batches (одно значение на весь batch). В /audio/transcriptions и /images/edits это обычное поле формы: -F user=customer-42. Бесплатные /moderations в журнал не пишутся, поэтому и user у них не сохраняется.

Что AITUNNEL делает со значением

  • Записывает в журнал запросов вместе со стоимостью, токенами и временем.
  • Отдаёт в GET /aitunnel/requests/{id} полем user.
  • Считает расход по нему в GET /aitunnel/usage: group_by=user и фильтр user=….
  • Показывает в кабинете: в окне запроса в истории расходов («Ваш user») и в CSV-выгрузке, столбец User.

Что передавать

  • Постоянный и непрозрачный ID. Тот же человек — всегда то же значение: UUID, номер из вашей базы, хеш от почты с солью. Тогда расход по нему складывается в одну строку.
  • Не персональные данные. Почта, телефон и имя не нужны: значение хранится вместе с запросом около трёх месяцев и видно в вашем кабинете и в выгрузках.
  • Одно значение на пользователя, не на сессию. Идентификатор сессии или чата даст тысячи строк, и расход не сложится.
  • Пустая строка и значение не строкой игнорируются, как будто поля нет.

Что с этим делать

Расход по клиентам за месяц

curl
curl "https://api.aitunnel.ru/v1/aitunnel/usage?period=month&group_by=user" \
  -H "Authorization: Bearer sk-aitunnel-xxx"
Ответ
{
  "object": "usage",
  "period": { "from": "2026-10-01T00:00:00.000Z", "to": "2026-10-31T17:00:00.000Z", "timezone": "UTC", "preset": "month" },
  "group_by": ["user"],
  "totals": { "requests": 1840, "cost_rub": 212.4, "...": "поля как в /docs/api/usage" },
  "data": [
    { "user": "customer-42", "requests": 910, "cost_rub": 131.8, "...": "" },
    { "user": "customer-7", "requests": 640, "cost_rub": 61.2, "...": "" },
    { "user": "customer-19", "requests": 290, "cost_rub": 19.4, "...": "" }
  ]
}

Строки идут по убыванию расхода: самые дорогие клиенты первыми. Из этого легко получить счёт для клиента:

Python
import requests

H = {"Authorization": "Bearer sk-aitunnel-xxx"}
MARKUP = 1.5  # ваша наценка

usage = requests.get(
    "https://api.aitunnel.ru/v1/aitunnel/usage",
    params={"period": "last_month", "group_by": "user"},
    headers=H,
).json()

for row in usage["data"]:
    price = round(row["cost_rub"] * MARKUP, 2)
    print(f'{row["user"]}: {row["requests"]} запросов, себестоимость {row["cost_rub"]} ₽, счёт {price} ₽')

Один клиент по дням

curl
curl "https://api.aitunnel.ru/v1/aitunnel/usage?user=customer-42&period=30d&group_by=day" \
  -H "Authorization: Bearer sk-aitunnel-xxx"

С user работают те же period, from/to, timezone, model, endpoint и group_by=day/hour/model.

Разобрать жалобу клиента

Сохраняйте id ответа (chat-…, resp-…, msg-…) рядом с сообщением в своей базе. Когда клиент пишет «бот ответил обрезанно» или «долго думал», запрос открывается по этому id:

Python
import requests

H = {"Authorization": "Bearer sk-aitunnel-xxx"}

# request_id вы сохранили при ответе клиенту
info = requests.get(
    "https://api.aitunnel.ru/v1/aitunnel/requests/chat-034pYx9KaOzvCxWdvYoAvp",
    headers=H,
).json()

print(info["user"])           # customer-42
print(info["finish_reason"])  # length: ответ оборвался на лимите токенов
print(info["ttft_ms"])        # время до первого токена, если был стрим
print(info["cost_rub"])

finish_reason: "length" значит, что ответ оборвался на лимите токенов, ttft_ms — время до первого токена потока. Все поля — на странице Информация о запросе.

Ограничения

  • Только новые запросы. Значение пишется с момента, когда вы начали его передавать. Прошлый расход по user не восстановить.
  • Журнал хранится около трёх месяцев, поэтому group_by=user и user=… считают период до 92 дней.
  • Запросы без user в такой выборке не участвуют. Итог totals при group_by=user поэтому меньше общего расхода ключа. Общий расход — запрос без user.
  • У batch и видео — одно значение на задачу. Расход задачи записывается одной строкой, когда она завершилась, с тем user, что был в запросе на её создание.
  • Это учёт, не лимит. Ограничить расход отдельного клиента через user нельзя. Для этого выдайте ему отдельный ключ с бюджетом.
  • Расход считается по ключу, которым сделаны запросы. Другой ключ — другие цифры.