QannasAPI

تعامل مع عدم العثور وإعادة المحاولة دون دفع مرتين

ما ستبنيه: غلاف استدعاء يميز ثلاث نتائج — وُجد، ولم يُوجد، وفشل — ويعيد المحاولة للثالثة فقط، مع مفتاح تكرار يمنع أي خصم مزدوج.

القسمأساسيات الواجهة
الإجراءات المستخدمةPOST /v1/data/contacts/find-email
الائتمانات5 (أسوأ الحالات)
آمن مع مفتاح الاختبارنعم — يعمل على qk_test_ بصفر ائتمان
مدة الإنجاز~10 دقيقة
المتطلباتمفتاح، وعميل يستطيع ضبط ترويسات الطلب.

ثلاث نتائج لا نتيجتان

النتيجة الموجودة هي HTTP 200 مع data ممتلئة وخصم غير صفري. وعدم العثور هو HTTP 200 مع data: null و X-Credits-Charged: 0 — جرى البحث بأمانة ولم يجد شيئًا، فلم يُحاسَب شيء. أما الفشل فهو 4xx أو 5xx. الثالثة وحدها تستحق إعادة المحاولة.

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

لا تُعِد المحاولة عند عدم العثور

عدم العثور إجابة حقيقية عن الواقع، لا خطأ عابرًا. إعادة المحاولة لن تستحضر نتيجة؛ إنما تضيف زمن انتظار. خزِّن النتيجة السالبة لمدة معقولة وامضِ.

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

أعد المحاولة عند الفشل مع مفتاح تكرار

تقبل نقاط البيانات ترويسة Idempotency-Key. أرسل مفتاحًا ثابتًا لكل عملية منطقية، فتؤول إعادة المحاولة بعد انتهاء المهلة إلى النتيجة الأصلية بدل استدعاء ثانٍ مدفوع — وهو الأهم حين لا ترى الاستجابة الأولى أصلًا.

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

تراجع تدريجيًا، ولا تُعِد أبدًا عند 4xx

أعد المحاولة عند 5xx وانتهاء المهلة بتراجع أسّي مع تشويش. أما 422 فيعني أن جسم الطلب خاطئ: أصلحه بدل إعادة إرساله. أعطال المصادر لا تُحاسَب أبدًا، فإعادة المحاولة بعد فشل حقيقي لا تكلفك شيئًا إضافيًا.

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

الخلاصة

النمطالطريقة
وُجد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.
  • خزِّن نتائج عدم العثور حتى لا تعيد السؤال نفسه طوال اليوم.

الخطوات التالية

شغِّل التقدير أولًا. لا يكلف شيئًا، وهو الحساب نفسه الذي سيطبقه العدّاد.