Пользователи вашего сервиса (user)
Передавайте в запросах идентификатор своего пользователя: по нему можно посчитать расход по клиентам, найти запрос при жалобе и выставить счёт.
user в теле любого платного метода; в /messages — metadata.user_id; в multipart-запросах (/audio/transcriptions, /images/edits) — поле формы userПоле необязательное и ничего не меняет в ответе модели и в цене.
Как передать
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": "Привет!" }]
}'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-034pYx9KaOzvCxWdvYoAvpAnthropic Messages принимает идентификатор в metadata.user_id:
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 "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, "...": "" }
]
}Строки идут по убыванию расхода: самые дорогие клиенты первыми. Из этого легко получить счёт для клиента:
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 "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:
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нельзя. Для этого выдайте ему отдельный ключ с бюджетом. - Расход считается по ключу, которым сделаны запросы. Другой ключ — другие цифры.