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.
| Section | Fondamentaux de l'API |
|---|---|
| Actions utilisées | POST /v1/data/contacts/find-email |
| Crédits | 5 (pire cas) |
| Compatible clé de test | Oui — tourne sur qk_test_ pour zéro crédit |
| Temps nécessaire | ~10 min |
| Prérequis | Une 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.
# 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
# < 503Ne 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 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.
# 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.
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é
| Motif | Comment |
|---|---|
| Trouvé | 200, data rempli, X-Credits-Charged > 0 |
| Vide | 200, data: null, X-Credits-Charged: 0 — ne pas relancer |
| Échec | 4xx / 5xx — ne relancer que les 5xx et les timeouts |
| Reprise sûre | En-tête Idempotency-Key, stable par opération logique |
| Requête invalide | 422 — corrigez le corps, ne le renvoyez jamais tel quel |
| Backoff | Exponentiel 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.