Обрабатывайте пустые ответы и повторы, не платя дважды
Что вы построите: обёртку вызова, различающую три исхода — найдено, не найдено и сбой — и повторяющую только третий, с ключом идемпотентности, чтобы повтор никогда не списал дважды.
| Раздел | Основы API |
|---|---|
| Используемые действия | POST /v1/data/contacts/find-email |
| Кредиты | 5 (худший случай) |
| Работает с тестовым ключом | Да — работает на qk_test_ за ноль кредитов |
| Время на выполнение | ~10 мин |
| Требования | Ключ и клиент, умеющий задавать заголовки запроса. |
Три исхода, а не два
Найденный результат — это HTTP 200 с заполненным data и ненулевым списанием. Пустой результат — это HTTP 200 с data: null и X-Credits-Charged: 0: поиск честно отработал и ничего не нашёл, поэтому ничего не списано. Сбой — это 4xx или 5xx. Повторять стоит только третий.
# 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 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. Отправляйте стабильный ключ на каждую логическую операцию, и повтор после тайм-аута разрешится в исходный результат вместо второго платного вызова — что важнее всего, когда вы вообще не увидели первый ответ.
# 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 означает, что тело запроса неверно: исправьте его, а не отправляйте снова. Сбои источников никогда не тарифицируются, поэтому повтор после настоящего сбоя не стоит вам ничего дополнительно.
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.
- Кэшируйте пустые результаты, чтобы не задавать один и тот же вопрос весь день.
Дальше
Сначала запустите оценку. Она ничего не стоит и использует ровно ту же арифметику, что и счётчик.