صورة جواز السفر هي أكثر ملف حساس تتلقاه معظم التطبيقات على الإطلاق. فيها وجه واسم كامل وتاريخ ميلاد ورقم وثيقة، وهي تكفي لفتح حساب في مكان آخر. ومع ذلك تُنسخ الصورة في كثير من مسارات الرفع خمس أو ست مرات قبل أن يسأل أحد هل يجب أن تكون موجودة أصلًا.

يتناول هذا المقال المسألة من الجانب الهندسي. ينقل ما يقوله GDPR عن الاحتفاظ بالبيانات، ويمر على الأماكن التي تتراكم فيها صورة جواز السفر بصمت، ويصف نمطًا نسميه اقرأ، أعِد، انسَ: تُقرأ الصورة، وتُعاد النتيجة، ولا يُحتفظ بشيء من الصورة. ويختم بالجزء الذي يبقى مسؤوليتك، لأن بعض الشركات ملزمة بالاحتفاظ بنسخة، وابتداءً من يوليو 2027 ينص الاتحاد الأوروبي على ذلك صراحة في قانون جديد.

هذه مدونة doc.cheap، وهي واجهة OCR لجوازات السفر وبطاقات الهوية تقرأ وثائق الهوية وتعيد النتيجة بصيغة JSON. اقرأ الأجزاء المتعلقة بالمنتج مع أخذ ذلك في الحسبان.

ما الذي يطلبه GDPR فعلًا

لا يقول GDPR "لا تخزّن صورة جواز السفر أبدًا". بل يقول شيئًا أنفع: احتفظ بما تحتاجه، طوال المدة التي تحتاجه فيها، ولا أكثر. تحدد Art. 5(1) من Reg. (EU) 2016/679 المبادئ. واثنان منها يحسمان معظم التصميم.

المبدأ نص Art. 5(1) (النسخة الإنجليزية) ماذا يعني للصورة
تقليل البيانات، البند (c) "adequate, relevant and limited to what is necessary in relation to the purposes for which they are processed" إذا كانت عمليتك تحتاج إلى الاسم وتاريخ الميلاد ورقم الوثيقة، فقد لا تعود الصورة نفسها ضرورية بعد قراءتها.
تقييد التخزين، البند (e) "kept in a form which permits identification of data subjects for no longer than is necessary for the purposes for which the personal data are processed" كل نسخة تحتاج إلى تاريخ انتهاء، و"لم نجد وقتًا لحذفها" ليس تاريخ انتهاء.

وتضيف Art. 5(2) مبدأ المساءلة: يجب أن تكون قادرًا على إثبات أنك تلتزم بهذه المبادئ. أما Art. 25(1)، أي حماية البيانات منذ التصميم، فتطلب "appropriate technical and organisational measures, such as pseudonymisation, which are designed to implement data-protection principles, such as data minimisation, in an effective manner"، أي تدابير تقنية وتنظيمية مناسبة، مثل استخدام الأسماء المستعارة، مصممة لتطبيق مبادئ مثل تقليل البيانات تطبيقًا فعالًا.

حين تُقرأ معًا، تحوّل سؤالًا قانونيًا إلى سؤال هندسي. كلما قلّت نسخ الصورة، قلّت الأماكن التي عليك وصفها وتأمينها ونسخها احتياطيًا ثم تفريغها في النهاية. النسخة التي لم تُكتب قط هي الوحيدة التي لا تحتاج إلى أي من هذا العمل.

أين تنتهي صور جوازات السفر

تخزّن معظم الفرق الصورة عن قصد في مكان واحد. المشكلة في الأماكن التي لم يخترها أحد. إليك قائمة لتراجعها مقابل مسارك.

المكان كيف تصل الصورة إليه
حاوية الرفع (bucket) يرفع العميل الملف إلى تخزين الكائنات أولًا، ثم يقرؤه الـ backend من هناك. يبقى الكائن بعد انتهاء الطلب.
سجلات الطلبات تكتب طبقة middleware للتسجيل أجسام الطلبات، وصورة base64 هي جسم طلب.
تقارير الأخطاء تُرفق أداة تتبع الاستثناءات الـ payload الخاص بالطلب الذي فشل.
الطوابير وإعادة المحاولة تحمل رسالة مهمة الصورة، ويحتفظ طابور الرسائل الميتة (dead-letter) بالفاشلة منها لأسابيع.
النسخ الاحتياطية واللقطات لقطة قاعدة بيانات أو قرص أُخذت في ذلك اليوم تحتفظ بكل صورة كُتبت قبلها، حتى بعد حذف الصف بوقت طويل.
تذاكر الدعم يرسل المستخدم الصورة مرة أخرى بالبريد "لأن الرفع لم ينجح".
التحليلات وإعادة تشغيل الجلسات تسجّل أداة ما الصفحة، بما في ذلك معاينة الملف المختار.
مزوّد OCR تحتفظ الخدمة التي تقرأ الوثيقة بنسختها الخاصة، وفق قواعد الاحتفاظ الخاصة بها.

الصف الأخير هو الذي تتحكم فيه أقل من غيره. يمكنك إصلاح سجلاتك بنفسك. أما النسخة التي يحتفظ بها مزوّد فتحكمها إعدادات ذلك المزوّد، وعليك أن تسأل عنها.

النمط: اقرأ، أعِد، انسَ

النمط بسيط في صياغته. توجد الصورة في الذاكرة فقط، طوال مدة طلب واحد. ما يخرج من الطلب هو القراءة: القيم المستخرجة. أما الصورة فلا تخرج إطلاقًا.

  1. أرسل الصورة مباشرة إلى التعرف. لا حاوية رفع في المنتصف. وإذا كانت الحاوية لا مفر منها للملفات الكبيرة، فاجعل عمر الكائن بضع دقائق واحذفه عند عودة الاستدعاء.
  2. استخدم ما تحتاجه من الاستجابة فورًا. القصاصات، مثل صورة حامل الوثيقة، موجودة في تلك الاستجابة فقط. إذا كان مسارك يقارن صورة سيلفي بالصورة الشخصية، فافعل ذلك الآن.
  3. احتفظ بالقراءة لا بالصورة. خزّن الحقول التي تحتاجها عمليتك، وفق قاعدة الاحتفاظ الخاصة بك. تاريخ الميلاد ورقم الوثيقة لا يزالان بيانات شخصية، لذا يحتاجان بدورهما إلى تاريخ انتهاء.
  4. أبقِ الصورة خارج السجلات وتقارير الأخطاء. أزل أجسام الطلبات عند الحدود، في مكان واحد، بدل الاعتماد على أن يتذكر كل مستدعٍ ذلك.
  5. دوّن النتيجة. لكل مكان في الجدول أعلاه، سجّل هل يمكن أن تصل إليه الصورة، ولماذا لا. هذه الملاحظة هي المساءلة التي تطلبها Art. 5(2).

ماذا تفعل واجهتنا بالصورة

إليك كيف يتعامل doc.cheap مع السؤال نفسه، كما تصفه صفحة الاحتفاظ بالبيانات والخصوصية.

  • لا تُخزَّن الصورة أبدًا. تعيش في الذاكرة طوال الطلب، وتُسلَّم إلى محرك التعرف، وتختفي عند كتابة الاستجابة. لا يتلقاها أي قرص أو مخزن كائنات أو سجل.
  • ولا تُخزَّن القصاصات أيضًا. قصاصة الوثيقة وصورة حاملها والتوقيع تعود في استجابة الاستدعاء الذي أنتجها. وأي مسح يُقرأ لاحقًا عبر GET /v1/scans/{id} تكون كل خانات الصور فيه null.
  • ما يمكن الاحتفاظ به هو القراءة، وفقط للمدة التي تطلبها. يحددها الخيار retain_hours لكل طلب، من 0 حتى 8760 ساعة (سنة واحدة). والقيمة الصريحة تتقدم دائمًا على إعداد الحساب.
  • retain_hours: 0 لا يكتب شيئًا. ليس صفًا تنتهي صلاحيته فورًا: لا صف على الإطلاق. لا شيء يحتاج إلى تنظيف، ولا شيء في نسخة احتياطية، ولا شيء للتصدير. ومع ذلك يُحتسب المسح مسحًا.
  • ويغطي الإعداد الافتراضي للحساب ما تبقى. حين لا يحدد الطلب مدة، يُطبَّق إعداد السجل الخاص بالحساب: 24 ساعة أو 7 أيام أو شهر واحد أو سنة واحدة. تبدأ الحسابات الجديدة بسنة واحدة، فتعرض لوحة التحكم سجلًا. وتقصير الإعداد يسري على الصفوف المخزنة مسبقًا، ويُحسب كل منها من وقت إنشائه.
  • الصف المحفوظ يحتفظ بصورة صغيرة واحدة: صورة مصغرة لا يتجاوز ضلعها الأطول 96 px ولا يتجاوز حجمها 16 KiB، تظهر في سجل العمليات في لوحة التحكم ليمكن التعرف على الصف. لا يمكن قراءتها عبر الـ API. وتذهب الصورة المصغرة مع ذهاب الصف.
  • يمكن حذف مسح واحد مبكرًا. يرسل مفتاح live الطلب DELETE /v1/scans/{id}، فيُزيل النتيجة وصف السجل والصورة المصغرة. والحذف نهائي.

يشرح دليل التحكم في مدة الاحتفاظ بالسجل الإعدادات خطوة بخطوة، وتقدم صفحتنا عن كيفية معالجتنا للبيانات الملخص.

إليك استدعاءً بلا احتفاظ في Python باستخدام requests. مفتاح الـ sandbox العام sk_sandbox_public منشور في الوثائق ولا يحتاج إلى تسجيل: يمنح 10 مستندات معترف بها مجانًا لكل عنوان إجمالًا، و10 طلبات في الساعة كحد أقصى. عدم الاحتفاظ إعداد في حسابك أنت، لذا يحتاج إلى مفتاحك الحي (live). أما الـ sandbox العام فليس حسابًا: فهو يحتفظ بسجل لكل مسح، مع صورته الصغيرة، في سجل الخدمة نفسها، لذا أرسل إليه صورة اختبار، ولا ترسل مستندًا حقيقيًا أبدًا.

import base64
import uuid

import requests

API = "https://api.doc.cheap/v1/scans"
KEY = "sk_sandbox_public"  # مفتاح live الخاص بك في بيئة الإنتاج


def read_and_forget(path):
    with open(path, "rb") as f:
        image = base64.b64encode(f.read()).decode("ascii")
    response = requests.post(
        API,
        headers={
            "Authorization": f"Bearer {KEY}",
            "Idempotency-Key": str(uuid.uuid4()),
        },
        json={
            "image": image,
            # 0: مع مفتاح حي، لا يُكتب أي شيء عن هذا المسح لدى الـ API.
            # False: بلا قصاصة للصورة الشخصية، لأن هذا المسار لا يستخدمها.
            "options": {"retain_hours": 0, "return_portrait": False},
        },
        timeout=30,
    )
    response.raise_for_status()
    scan = response.json()
    del image  # تختفي النسخة المحلية فور عودة الاستدعاء
    if scan["meta"]["status"] != "recognized":
        return None
    # احتفظ بالقراءة التي تحتاجها عمليتك، وفق قاعدة الاحتفاظ الخاصة بك.
    return {
        "scan_id": scan["meta"]["id"],
        "document_number": scan["document"]["number"],
        "expiry_date": scan["document"]["expiry_date"],
        "birth_date": scan["holder"]["birth_date"],
        "mrz_status": scan["mrz"]["status"],
    }

للاحتفاظ الصفري ثمن واحد يستحق أن تعرفه قبل أن يفاجئك. عادةً يتيح Idempotency-Key لإعادة المحاولة أن تعيد النتيجة الأولى. ومع retain_hours: 0 لا توجد نتيجة مخزنة لإعادتها، لذا تُرفض إعادة المحاولة بالمفتاح نفسه طوال 24 ساعة بالحالة HTTP 409 والرمز idempotency_replay_unavailable، بدل الإجابة مرتين. اعتبر هذه الإجابة دليلًا على أن "الاستدعاء الأول نجح"، واستخدم النتيجة التي لديك بالفعل.

قراءة المسح مرة أخرى تُظهر الوجه الآخر للتصميم. مفتاح الـ sandbox لا يقرأ أي شيء مرة أخرى، أيًّا كان الـ id. أرسلنا هذا الطلب باستخدام sk_sandbox_public في 5 أكتوبر 2026:

curl https://api.doc.cheap/v1/scans/<SCAN_ID> \
  -H "Authorization: Bearer sk_sandbox_public"

وعاد بالحالة HTTP 404 (الرسالة مختصرة):

{
  "error": {
    "code": "not_found",
    "message": "No scan with id …",
    "docs_url": "https://doc.cheap/docs/errors/not_found"
  }
}

ويتلقى مفتاح live الحالة 404 نفسها لمسح أُجري مع retain_hours: 0، ولأي مسح بعد انقضاء مدته. إذا كنت تقارن بين الخدمات في هذه النقطة، فإن مقارنة واجهات OCR لجوازات السفر مكان مناسب للبدء؛ اسأل كل خدمة إلى أين تذهب الصورة، لا عمّا تعيده فقط.

ما يبقى مسؤوليتك

اقرأ، أعِد، انسَ يزيل النسخ لدى الـ API. لكنه لا يقرر ما يجب على شركتك الاحتفاظ به. بالنسبة لبعض الشركات الجواب هو "نسخة"، والقانون يقول ذلك.

يُطبَّق قانون الاتحاد الأوروبي الجديد لمكافحة غسل الأموال، Reg. (EU) 2024/1624، ابتداءً من 10 يوليو 2027. تقول Art. 90: "It shall apply from 10 July 2027, except in relation to obliged entities referred to in Article 3, points (3)(n) and (o), to which it shall apply from 10 July 2029." أي أنه يُطبَّق من 10 يوليو 2027، باستثناء الجهات الملزمة المشار إليها في Art. 3، البندين (3)(n) و(o)، التي يُطبَّق عليها من 10 يوليو 2029. وتُلزم Art. 77 بشأن الاحتفاظ بالسجلات الجهاتِ الملزمة، مثل البنوك والشركات المالية الأخرى، بالاحتفاظ بما يلي:

"a copy of the documents and information obtained in the performance of customer due diligence pursuant to Chapter III, including information obtained through electronic identification means;"

بعبارة أخرى، نسخة من الوثائق والمعلومات التي حُصل عليها أثناء إجراءات العناية الواجبة تجاه العملاء، بما فيها المعلومات التي حُصل عليها عبر وسائل التعريف الإلكتروني.

وتحدد Art. 77(3) المدة: تُحفظ السجلات "retained for a period of 5 years commencing on the date of the termination of the business relationship"، أي لمدة 5 سنوات تبدأ من تاريخ انتهاء علاقة العمل، ثم "obliged entities shall delete personal data upon expiry of the five-year period"، أي تحذف الجهات الملزمة البيانات الشخصية عند انقضاء السنوات الخمس. وتسمح Art. 77(2)، بشروط، بـ "a retention of the references to such information"، أي الاحتفاظ بالإشارات إلى تلك المعلومات بدل النسخ.

لذلك، إن كنت جهة ملزمة، فإن الاحتفاظ الصفري لدى الـ API لا يُسقط واجبك في الاحتفاظ بسجل. بل يغيّر مكان وجود السجل. يصبح مخزنك الخاص هو النسخة الوحيدة، ويظل مبدأ تقييد التخزين المذكور أعلاه ساريًا عليه: بعد خمس سنوات من انتهاء العلاقة، يُحذف. والعمل التصميمي هو أن تجعل ذلك المخزن مقصودًا: مكان واحد، ومسؤول واحد، وتشفير، وتحكم في الوصول، ومهمة حذف، بدل الكومة العرضية التي في الجدول أعلاه.

وإن لم تكن جهة ملزمة، فاطرح السؤال البسيط أولًا: هل يحتاج أي شيء في عمليتك إلى الصورة بعد قراءة الحقول؟ غالبًا ما يكون الجواب الصادق: لا.

قائمة تحقق

  • كل مكان في جدول "أين تنتهي الصور" رُوجع مقابل مسارك.
  • تذهب الصورة مباشرة إلى التعرف، أو تمر عبر حاوية عمرها بضع دقائق.
  • تُستخدم القصاصات داخل معالج الاستجابة ولا تُكتب في أي مكان.
  • يحدد استدعاء OCR مدة الاحتفاظ عن قصد، retain_hours: 0 حين لا يلزم قراءة أي شيء مرة أخرى.
  • تتعامل إعادة المحاولة مع الإجابة 409 idempotency_replay_unavailable.
  • تزيل السجلات وتقارير الأخطاء أجسام الطلبات عند حد واحد.
  • للحقول التي تحتفظ بها تاريخ انتهاء، وهناك ما يحذفها.
  • إذا اشترط قانون ما نسخة، فإنها تُحفظ في مخزن واحد مقصود له تاريخ حذف خاص به.

هذا ملخص هندسي، وليس استشارة قانونية. إن وجدت مكانًا يمكن أن تتسرب منه صورة ولم يذكره هذا المقال، فاكتب إلى admin@doc.cheap.

سؤال لك: أين وجدت آخر مرة نسخة من وثيقة هوية لم يقصد أحد الاحتفاظ بها؟ أخبرنا في التعليقات أدناه.