# Баланс и стоимость

Бюджет и расходы показывайте в рублях с точностью до микрорубля: от двух до шести знаков после запятой. Например, **0,99975 ₽** и **0,000001 ₽**. Положительная стоимость не должна превращаться в «0,00 ₽». Для расчётов сохраняйте целые значения API.

## Суммы в API

Денежные поля API содержат целые числа: **1 ₽ = 1 000 000 микрорублей**. Например, значение API `999750` отображается как **0,99975 ₽**. `GET /v1/balance` требует `balance:read` и возвращает `availableMicrorub`, `reservedMicrorub`, `currency=RUB` и `unit=microrub`. Имена полей и значение `unit` не меняются.

Для положительных сумм этот пример принимает бюджет в рублях и показывает ответ API без потери исходного значения:

```javascript
function rublesToApiUnits(rubles) {
  if (!/^\d+(?:\.\d{1,6})?$/.test(rubles)) {
    throw new Error('Введите рубли строкой с точкой и не более шести знаков');
  }
  const [whole, fraction = ''] = rubles.split('.');
  const amount = BigInt(whole) * 1_000_000n
    + BigInt(fraction.padEnd(6, '0'));
  if (amount > BigInt(Number.MAX_SAFE_INTEGER)) {
    throw new Error('Сумма превышает допустимый предел API');
  }
  return Number(amount);
}

function formatRubles(apiAmount) {
  if (!Number.isSafeInteger(apiAmount) || apiAmount < 0) {
    throw new Error('Ожидается неотрицательная целая сумма API');
  }
  const amount = BigInt(apiAmount);
  const fraction = String(amount % 1_000_000n).padStart(6, '0')
    .replace(/0+$/, '').padEnd(2, '0');
  return String(amount / 1_000_000n) + ',' + fraction + ' ₽';
}

const budgetRub = '10.00';
const maxCostApi = rublesToApiUnits(budgetRub);
console.log(formatRubles(maxCostApi)); // 10,00 ₽
console.log(formatRubles(999750)); // 0,99975 ₽
console.log(formatRubles(1)); // 0,000001 ₽
```

Для показа используйте `formatRubles`, отправляйте исходное целое число. Не рассчитывайте платёж из форматированной строки. Код выше подходит для шага с JavaScript в Kvantora Flow.

## Резерв и списание

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

Для текста используйте `POST /v1/chat/quote` с тем же телом, которое готовите для `/v1/chat/completions`: `model`, `messages`, `max_tokens` и при необходимости `max_cost_microrub`. Ответ содержит `estimated_cost_microrub`, `input_tokens_estimate`, `max_output_tokens`, `expires_at`, `reservation_created=false` и `estimation_only=true`. Оценка входа приблизительная; фактические токены определяются после выполнения. Quote не создаёт резерв, историю или вызов модели.

Оценка действует 30 секунд и не фиксирует цену до отправки. Запрос генерации заново проверяет текущую оценку и `max_cost_microrub`; при превышении предела возвращает HTTP 409 до платного вызова. Слишком маленький предел в самой оценке также даёт HTTP 409. Полный путь показан в [быстром старте](/docs/quickstart.md).

Медиа сейчас используют фиксированную цену за запрос. `POST /v1/quotes` возвращает `estimated_cost_microrub`, `expires_at` и `reservation_created=false`. Можно передать оценку без изменений в `max_cost_microrub` либо задать собственный предел через `rublesToApiUnits(budgetRub)`. Если текущая цена выше предела, запрос не отправится.

## Оценка запроса медиа

В оценку передайте параметры будущей генерации и нужную `operation`. Этот запрос не создаёт задание и не резервирует деньги.

```javascript
const parameters = {
  model: process.env.KVANTORA_MODEL_ID,
  prompt: 'Лесное озеро на рассвете',
  n: 1,
};
const response = await fetch(process.env.KVANTORA_BASE_URL + '/v1/quotes', {
  method: 'POST',
  redirect: 'error',
  headers: {
    Authorization: 'Bearer ' + process.env.KVANTORA_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ ...parameters, operation: 'image_generation' }),
});
if (!response.ok) throw new Error('Оценка не получена: HTTP ' + response.status);
const quote = await response.json();
console.log(formatRubles(quote.estimated_cost_microrub));
```

Функция `formatRubles` определена выше. Проверяйте `expires_at` перед отправкой. Для генерации передайте `parameters` и точное `max_cost_microrub`, но не добавляйте `operation`: её задаёт endpoint. Оценка не гарантирует доступность модели к моменту отправки.

## История расходов

`GET /v1/usage?days=30` требует `usage:read`. `requests` считает принятые запросы проекта; `spent` суммирует подтверждённые списания по времени их завершения. Резервы не входят в `spent`. Период начинается по календарным дням в `billing_timezone` организации и заканчивается `as_of`.
