Lide com resultados vazios e retentativas sem pagar duas vezes
O que você vai construir: um wrapper de chamada que distingue três desfechos — encontrado, não encontrado e falha — e só repete o terceiro, com idempotência para que uma retentativa nunca cobre em dobro.
| Seção | Fundamentos da API |
|---|---|
| Ações usadas | POST /v1/data/contacts/find-email |
| Créditos | 5 (pior caso) |
| Funciona com chave de teste | Sim — roda em qk_test_ por zero créditos |
| Tempo estimado | ~10 min |
| Pré-requisitos | Uma chave e um cliente capaz de definir cabeçalhos de requisição. |
São três desfechos, não dois
Um resultado encontrado é HTTP 200 com data preenchido e cobrança diferente de zero. Um resultado vazio é HTTP 200 com data: null e X-Credits-Charged: 0 — a busca rodou honestamente e não achou nada, então nada foi cobrado. Uma falha é 4xx ou 5xx. Só a terceira vale retentativa.
# 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
# < 503Não repita um resultado vazio
Um resultado vazio é uma resposta real sobre o mundo, não um erro transitório. Repetir não vai fazer surgir um resultado; só acrescenta latência. Guarde em cache por uma janela razoável e siga em frente.
# 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"]Repita falhas com idempotência
Os endpoints de dados aceitam o cabeçalho Idempotency-Key. Envie uma chave estável por operação lógica e uma retentativa após timeout se resolve no resultado original em vez de numa segunda chamada cobrada — o que mais importa quando você nunca chegou a ver a primeira resposta.
# 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"}'Recue e nunca repita um 4xx
Repita 5xx e timeouts com backoff exponencial e jitter. Um 422 significa que o corpo da requisição está errado: corrija em vez de reenviar. Falhas de origem nunca são cobradas, então repetir depois de uma falha real não custa nada a mais.
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")Resumo
| Padrão | Como |
|---|---|
| Encontrado | 200, data preenchido, X-Credits-Charged > 0 |
| Vazio | 200, data: null, X-Credits-Charged: 0 — não repetir |
| Falha | 4xx / 5xx — repita apenas 5xx e timeouts |
| Retentativa segura | Cabeçalho Idempotency-Key, estável por operação lógica |
| Requisição inválida | 422 — corrija o corpo, nunca reenvie como está |
| Backoff | Exponencial com jitter e número de tentativas limitado |
Checklist de produção
- Ramifique por data == null antes de ramificar pelo código de status.
- Envie uma Idempotency-Key em tudo que você possa vir a repetir.
- Repita 5xx e timeouts; nunca repita um 422.
- Guarde resultados vazios em cache para não refazer a mesma pergunta o dia todo.
Próximos passos
Rode a estimativa primeiro. Ela não custa nada e aplica exatamente a mesma aritmética que o medidor.