QannasAPI

Обрабатывайте пустые ответы и повторы, не платя дважды

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

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

Три исхода, а не два

Найденный результат — это HTTP 200 с заполненным data и ненулевым списанием. Пустой результат — это HTTP 200 с data: null и X-Credits-Charged: 0: поиск честно отработал и ничего не нашёл, поэтому ничего не списано. Сбой — это 4xx или 5xx. Повторять стоит только третий.

three outcomes
# FOUND — data populated, charged
# < 200  X-Credits-Charged: 5
{ "status": "OK", "data": { /* … */ } }

# MISS — ran honestly, found nothing, billed zero
# < 200  X-Credits-Charged: 0
{ "status": "OK", "data": null }

# FAILURE — nothing billed, safe to retry
# < 503

Не повторяйте пустой результат

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

branch.py
# Branch on the payload BEFORE the status code:
# a miss and a hit are both HTTP 200.
if res.status_code >= 500:
    return retry_later()    # transient

if res.json()["data"] is None:
    cache_miss(query, ttl=86_400)  # a real answer — do not retry
    return None

return res.json()["data"]

Повторяйте сбои с идемпотентностью

Эндпоинты данных принимают заголовок Idempotency-Key. Отправляйте стабильный ключ на каждую логическую операцию, и повтор после тайм-аута разрешится в исходный результат вместо второго платного вызова — что важнее всего, когда вы вообще не увидели первый ответ.

idempotent.sh
# A stable key per logical operation. If you never saw the first
# response, the retry resolves to it instead of billing twice.
curl -X POST https://api.qannasapi.com/v1/data/contacts/find-email \
  -H "Authorization: Bearer $QANNAS_API_KEY" \
  -H "Idempotency-Key: signup-8f21c4" \
  -H "Content-Type: application/json" \
  -d '{"query": "Jane Doe, Acme Logistics"}'

Отступайте и никогда не повторяйте 4xx

Повторяйте 5xx и тайм-ауты с экспоненциальной задержкой и джиттером. Код 422 означает, что тело запроса неверно: исправьте его, а не отправляйте снова. Сбои источников никогда не тарифицируются, поэтому повтор после настоящего сбоя не стоит вам ничего дополнительно.

retry.py
import random, time

def call_with_retry(send, *, max_attempts=5):
    for attempt in range(max_attempts):
        res = send()

        if res.status_code == 422:
            raise ValueError(res.text)  # malformed — never resend

        if res.status_code < 500:
            return res

        # 5xx: upstream failures are never billed, so retrying is free
        time.sleep(0.5 * 2 ** attempt + random.uniform(0, 0.5))

    raise RuntimeError("exhausted retries")

Итоги

ПриёмКак
Найдено200, data заполнено, X-Credits-Charged > 0
Пусто200, data: null, X-Credits-Charged: 0 — не повторять
Сбой4xx / 5xx — повторять только 5xx и тайм-ауты
Безопасный повторЗаголовок Idempotency-Key, стабильный на логическую операцию
Неверный запрос422 — исправьте тело, никогда не отправляйте как есть
ОтступЭкспоненциальный с джиттером и ограничением числа попыток

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

  • Ветвитесь по data == null раньше, чем по коду статуса.
  • Отправляйте Idempotency-Key со всем, что может быть повторено.
  • Повторяйте 5xx и тайм-ауты; никогда не повторяйте 422.
  • Кэшируйте пустые результаты, чтобы не задавать один и тот же вопрос весь день.

Дальше

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