QannasAPI

Gérer les résultats vides et les reprises sans payer deux fois

Ce que vous allez construire : un wrapper d'appel qui distingue trois issues — trouvé, non trouvé et échec — et ne relance que la troisième, avec une clé d'idempotence pour qu'une reprise ne puisse jamais débiter deux fois.

SectionFondamentaux de l'API
Actions utiliséesPOST /v1/data/contacts/find-email
Crédits5 (pire cas)
Compatible clé de testOui — tourne sur qk_test_ pour zéro crédit
Temps nécessaire~10 min
PrérequisUne clé et un client capable de définir des en-têtes de requête.

Trois issues, pas deux

Un résultat trouvé est un HTTP 200 avec data rempli et un débit non nul. Un résultat vide est un HTTP 200 avec data: null et X-Credits-Charged: 0 — la recherche s'est faite honnêtement et n'a rien trouvé, donc rien n'a été facturé. Un échec est un 4xx ou un 5xx. Seul le troisième mérite une reprise.

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

Ne relancez pas un résultat vide

Un résultat vide est une vraie réponse sur le monde, pas une erreur passagère. Le relancer ne fera pas apparaître de résultat ; cela ajoute juste de la latence. Mettez-le en cache pour une durée raisonnable et passez à la suite.

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

Relancez les échecs avec idempotence

Les endpoints de données acceptent l'en-tête Idempotency-Key. Envoyez une clé stable par opération logique et une reprise après timeout se résout sur le résultat d'origine plutôt que sur un second appel facturé — ce qui compte surtout quand vous n'avez jamais vu la première réponse.

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

Temporisez, et ne relancez jamais un 4xx

Relancez les 5xx et les timeouts avec un backoff exponentiel et du jitter. Un 422 signifie que le corps de la requête est incorrect : corrigez-le au lieu de le renvoyer. Les défaillances en amont ne sont jamais facturées, donc une reprise après un véritable échec ne vous coûte rien de plus.

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

Résumé

MotifComment
Trouvé200, data rempli, X-Credits-Charged > 0
Vide200, data: null, X-Credits-Charged: 0 — ne pas relancer
Échec4xx / 5xx — ne relancer que les 5xx et les timeouts
Reprise sûreEn-tête Idempotency-Key, stable par opération logique
Requête invalide422 — corrigez le corps, ne le renvoyez jamais tel quel
BackoffExponentiel avec jitter, nombre de tentatives plafonné

Checklist de mise en production

  • Testez data == null avant de tester le code de statut.
  • Envoyez une Idempotency-Key sur tout ce que vous pourriez relancer.
  • Relancez les 5xx et les timeouts ; ne relancez jamais un 422.
  • Mettez les résultats vides en cache pour ne pas reposer la même question toute la journée.

Étapes suivantes

Lancez d'abord l'estimation. Elle ne coûte rien et applique exactement le même calcul que le compteur.