QannasAPI

Читайте заголовки кредитов: аудит каждого списания из ответа

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

РазделОсновы API
Используемые действияPOST /v1/data/contacts/verify-email
Кредиты1 (худший случай)
Работает с тестовым ключомДа — работает на qk_test_ за ноль кредитов
Время на выполнение~5 мин
ТребованияКлюч и любой HTTP-клиент, позволяющий читать заголовки ответа.

Три заголовка в каждом вызове данных

X-Credits-Charged — сколько стоил этот вызов. X-Credits-Balance — сколько осталось после него. X-Price-Book указывает прайс-лист, по которому рассчитано списание, поэтому оценку и списание всегда можно свести к одним и тем же правилам.

headers.sh
curl -i -X POST https://api.qannasapi.com/v1/data/contacts/verify-email \
  -H "Authorization: Bearer $QANNAS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "jane@acme.example"}'

# < HTTP/1.1 200 OK
# < X-Credits-Charged: 1      # what this call cost
# < X-Credits-Balance: 94     # what is left afterwards
# < X-Price-Book: public    # which rules were applied

Логируйте списание, а не оценку

Один раз оберните HTTP-клиент и записывайте три заголовка рядом с request_id. Это даёт распределение затрат по вызовам без опроса эндпоинта использования, а request_id — именно то, что запросит поддержка, если списание покажется неверным.

metered_client.ts
export async function call(action: string, body: unknown) {
  const res = await fetch(`${API}/v1/data/${action}`, {
    method: "POST",
    headers: { Authorization: `Bearer ${key}`, "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });

  const json = await res.json();
  // Your logs become the billing audit trail.
  logger.info("qannas.call", {
    action,
    request_id: json.request_id,
    charged: Number(res.headers.get("X-Credits-Charged")),
    balance: Number(res.headers.get("X-Credits-Balance")),
  });

  return json;
}

Следите за балансом, а не за часами

Поскольку баланс возвращается с каждым вызовом, ограничивать нагрузку или поднимать тревогу можно по уже полученному ответу. Нет отдельного эндпоинта квот для опроса и нет разрыва между тратой и знанием о ней.

throttle.ts
// The balance arrives on the call you already made —
// there is no quota endpoint to poll and no lag.
const balance = Number(res.headers.get("X-Credits-Balance"));
if (balance < LOW_WATER_MARK) {
  pauseNonUrgentJobs();
  alertOps(`credits low: ${balance}`);
}

Итоги

ПриёмКак
Сколько стоил вызовX-Credits-Charged
Сколько осталосьX-Credits-Balance
Какие правила применялисьX-Price-Book
Отследить списаниеrequest_id в теле ответа
Ограничение нагрузкиТревога по X-Credits-Balance из только что сделанного вызова

Чек-лист перед продакшеном

  • Логируйте X-Credits-Charged, X-Credits-Balance и request_id при каждом вызове.
  • Поднимайте тревогу по заголовку баланса, а не опрашивайте эндпоинт использования.
  • Храните request_id — по нему отслеживается конкретное списание.
  • Ожидайте X-Credits-Charged: 0 при пустом результате и не считайте это сбоем.

Дальше

Сначала запустите оценку. Она ничего не стоит и использует ровно ту же арифметику, что и счётчик.