QannasAPI

Gestiona resultados vacíos y reintentos sin pagar dos veces

Lo que vas a construir: un envoltorio de llamada que distingue tres desenlaces — encontrado, no encontrado y fallo — y reintenta solo el tercero, con idempotencia para que un reintento nunca pueda cobrar dos veces.

SecciónFundamentos de la API
Acciones usadasPOST /v1/data/contacts/find-email
Créditos5 (peor caso)
Compatible con clave de pruebaSí — se ejecuta con qk_test_ por cero créditos
Tiempo estimado~10 min
Requisitos previosUna clave y un cliente capaz de fijar cabeceras de petición.

Tres desenlaces, no dos

Un resultado encontrado es HTTP 200 con data poblada y un cargo distinto de cero. Un resultado vacío es HTTP 200 con data: null y X-Credits-Charged: 0 — la búsqueda se hizo con honestidad y no encontró nada, así que no se cobró nada. Un fallo es un 4xx o un 5xx. Solo el tercero merece reintento.

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

No reintentes un resultado vacío

Un resultado vacío es una respuesta real sobre el mundo, no un error transitorio. Reintentarlo no hará aparecer un resultado; solo añade latencia. Cachéalo durante una ventana razonable y sigue adelante.

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"]

Reintenta los fallos con idempotencia

Los endpoints de datos aceptan la cabecera Idempotency-Key. Envía una clave estable por operación lógica y un reintento tras un timeout se resuelve al resultado original en vez de a una segunda llamada cobrada — lo que más importa cuando nunca llegaste a ver la primera respuesta.

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"}'

Espera entre intentos y nunca reintentes un 4xx

Reintenta 5xx y timeouts con retroceso exponencial y jitter. Un 422 significa que el cuerpo de la petición es incorrecto: corrígelo en lugar de reenviarlo. Los fallos de origen nunca se cobran, así que reintentar tras un fallo real no te cuesta nada extra.

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")

Resumen

PatrónCómo
Encontrado200, data poblada, X-Credits-Charged > 0
Vacío200, data: null, X-Credits-Charged: 0 — no reintentar
Fallo4xx / 5xx — reintenta solo 5xx y timeouts
Reintento seguroCabecera Idempotency-Key, estable por operación lógica
Petición inválida422 — corrige el cuerpo, nunca lo reenvíes igual
RetrocesoExponencial con jitter y número de intentos limitado

Lista para producción

  • Bifurca por data == null antes de bifurcar por el código de estado.
  • Envía una Idempotency-Key en todo lo que puedas llegar a reintentar.
  • Reintenta 5xx y timeouts; nunca reintentes un 422.
  • Cachea los resultados vacíos para no repetir la misma pregunta todo el día.

Siguientes pasos

Ejecuta primero la estimación. No cuesta nada y aplica exactamente la misma aritmética que el contador.