{
  "openapi": "3.0.3",
  "info": {
    "title": "ProofSnap API",
    "version": "2.0.0",
    "license": {
      "name": "Proprietary",
      "url": "https://getproofsnap.com/terms.html"
    },
    "description": "تُحمِّل واجهة ProofSnap API صفحة الويب في متصفح Chromium يعمل على الخادم، وتُعيد حزمة أدلة (ZIP).\n\nتحتوي كل حزمة التقاط على لقطة الشاشة وHTML الصفحة والنص المستخرج من الصفحة وmetadata.json وforensic_log.json وchain_of_custody.json وevidence.pdf، وعلى ملف manifest.json الذي يسرد بصمة SHA-256 لكل ملف. يُوقَّع manifest.json بمفتاح RSA-4096 يُنشأ لعملية الالتقاط هذه (يُرفق المفتاح العام بوصفه publickey.pem، ويُتلَف المفتاح الخاص)، وتُرسَل بصمة SHA-256 لملف manifest.json إلى خوادم التقويم (calendars) في OpenTimestamps لربطها بسلسلة كتل Bitcoin (manifest.json.ots). إذا فشل الطلب إلى خادم التقويم، تُسلَّم الحزمة من دون هذا الإثبات ولا يُدرجه manifest.json، فتُبلغ البرامج النصية للتحقق دون اتصال وأداة التحقق بأنه غير موجود في الحزمة لا بأنه مفقود. عند تفعيل `include_eidas` باستخدام مفتاح فعلي، تحصل البصمة نفسها أيضًا على طابع زمني إلكتروني مؤهل وفق eIDAS (RFC 3161، manifest.json.tsr). تُوقَّع لقطات الشاشة بصيغة JPEG ببيانات اعتماد المحتوى C2PA Content Credentials قدر الإمكان: فإذا فشل التوقيع، تكتمل عملية الالتقاط من دونها. يضم المجلد verification/ برامج نصية تتحقق من الحزمة دون اتصال بالإنترنت. يذكر كل manifest.json القيمة `ots_listed_only_when_present: true` (لا يُدرَج manifest.json.ots إلا إذا كان موجودًا في الحزمة) والقيمة `eidas_marker_version: 1`. ولا يسجّل `eidas_requested: true` إلا إذا سُلِّمت الحزمة مع طابعها الزمني المؤهل. وإذا تعذّر الحصول على الرمز (token)، يُختم البيان من جديد من دونه. لذلك فإن الحزمة التي تسجّل `eidas_requested: true` ولكن ينقصها manifest.json.tsr قد فقدت رمزها، وتُبلغ البرامج النصية للتحقق دون اتصال بالنتيجة FAIL. يسجّل chain_of_custody.json القيمة `bitcoin_ots_included` ويسرد في `page_modifications.items[]` كل تغيير أُجري على الصفحة قبل الالتقاط. يتضمن manifest.json الحقل sealed_at (وقت إنشائه، بعد حساب بصمات كل الملفات) وفروق ساعة خادم الالتقاط المقيسة عبر SNTP؛ ويسرد chain_of_custody.json خطوات عملية الالتقاط بترتيب حدوثها، كلٌّ منها مع وقت انتهائها، ويُختتم بالخطوة custody_record_written (حساب البصمات والتوقيع والطوابع الزمنية تأتي بعدها، ويشهد عليها manifest.json والإثباتات).\n\nيسجّل metadata.json ما يلي:\n\n- `browser_identity`: وكيل المستخدم (user agent) وتلميحات العميل (Client Hints) المُرسَلة فعليًا.\n- `dns`: فحص DNS مستقل للعنوان الذي اتصل به المتصفح، مع إجابات dns.google وcloudflare-dns.com والنتيجة: confirmed أو not_confirmed أو resolvers_unavailable أو connected_ip_unknown أو skipped_ip_literal (يحدد عنوان URL عنوان IP، فلا يوجد اسم مضيف للبحث عنه) أو skipped_internal_host (اسم مضيف داخلي، لا يُرسَل أبدًا إلى خوادم التحليل العامة).\n- `url_at_load` و`url_at_screenshot`: العنوان مباشرةً بعد تحميل الصفحة ولحظة التقاط صورتها (قد تغيّر الصفحة عنوانها بنفسها، وغالبًا الجزء الواقع بعد #).\n- `redirect_chain`: عمليات إعادة التوجيه للمستند الرئيسي، من الأقدم إلى الأحدث، كلٌّ منها مع رمز حالة HTTP الخاص بها. يُذكر العنوانان المطلوب والنهائي كاملين؛ أما كل خطوة وسيطة بينهما فتُستبدل قيم الاستعلام فيها بـ [redacted] (`query_values_redacted`).\n- `tls.source`: مصدر كل حقل من حقول TLS، إما `browser_connection` أو `browser_connection_and_separate_handshake`، والثاني مع `at` و`ip` و`port` الخاصة بالمصافحة المنفصلة التي جلبت certificate_chain.pem بعد تحميل الصفحة.\n\nعندما لا تُنفَّذ خطوة مطلوبة (طابع زمني eIDAS، أو توقيع C2PA، أو طلب الأرشفة، أو WHOIS، أو القراءة الثانية للساعة، أو ملف WARC اقتُطع بسبب الحد الزمني، أو انتظار أو توسيع للمحتوى تم تخطيه بسبب الحد الزمني)، يذكرها evidence.pdf مع سبب موجز، وتسمّيها شهادة المنشأ (provenance certificate) في سطر واحد.\n\nعمليات الالتقاط غير متزامنة. تُعيد POST /captures الرمز 202 مع capture_id؛ استعلم دوريًا عبر GET /captures/{capture_id} أو مرِّر `options.webhook_url`. تُعيد عملية الالتقاط المكتملة رابطًا موقّعًا مسبقًا package_url صالحًا لمدة تصل إلى 24 ساعة (انظر package_url_expires_at)؛ اقرأ عملية الالتقاط مرة أخرى للحصول على رابط جديد.\n\nالمسارات أدناه نسبية إلى عنوان URL للخادم، الذي يتضمن بالفعل المرحلة /v1. تتضمن قيم `poll_path` التي تُعيدها الواجهة المقطع /v1.",
    "contact": {
      "email": "support@getproofsnap.com"
    }
  },
  "servers": [
    {
      "url": "https://api.getproofsnap.com/v1",
      "description": "ProofSnap API (EU)"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "عمليات الالتقاط",
      "description": "إنشاء عمليات الالتقاط وسردها وقراءتها وحذفها والتحقق منها.\n\n**حدود المعدّل.** تخضع `POST /captures` و`POST /captures/batch` و`POST /compare` و`POST /captures/{capture_id}/redact` و`POST /certify-hash` لحد معدّل لكل حساب ضمن نوافذ زمنية ثابتة مدتها 60 ثانية: تتشارك جميع مفاتيح API في الحساب وجلسة لوحة التحكم الخاصة به نافذة واحدة (إضافة مفاتيح لا ترفع الحد). تستهلك الدفعة (batch) أو توثيق بصمات متعددة وحدة واحدة لكل عنوان URL أو بصمة. الحدود الافتراضية لكل نافذة حسب الخطة:\n\n| الخطة | الطلبات خلال 60 ثانية |\n| --- | --- |\n| sandbox | 10 |\n| developer | 60 |\n| startup | 120 |\n| growth | 300 |\n| custom | 1200 |\n| legal_solo | 60 |\n| legal_seats | 120 |\n| website_archive | 60 |\n| archive_only | 30 |\n| أي خطة أخرى | 30 |\n\nتستهلك الطلبات التالية أيضًا من النافذة نفسها، وحدة واحدة لكل طلب ما لم يُذكر خلاف ذلك: `POST /captures/{capture_id}/verify`، و`PATCH /captures/{capture_id}` مع action legal_hold، و`GET /captures/hosts` (فقط عند إعادة حساب القائمة، لا عند إعادة نتيجة مخزنة مؤقتًا)، و`POST /legal-holds` (وحدة واحدة لكل 10 عمليات التقاط)، و`POST /captures/export-jobs`، وكذلك `POST /imports` و`POST /imports/{import_id}/complete` و`GET /imports/{import_id}?upload=true`.\n\nعند تجاوز الحد، تُعيد الواجهة 429 RATE_LIMITED مع الترويسات `Retry-After` و`X-RateLimit-Limit` و`X-RateLimit-Remaining` و`X-RateLimit-Reset` والحقل `error.details.retry_after_seconds`. أما الطلب الذي يحتاج إلى وحدات أكثر مما تسمح به نافذة واحدة (توثيق بصمات متعددة يتجاوز الحد لكل نافذة) فلا يمكن أن ينجح أبدًا، ويُعيد 400 RATE_LIMIT_COST_TOO_HIGH مع `error.details` `{ max_per_window, window_seconds, requested }`. قسّمه إلى طلبات أصغر. لا يستهلك الطلب المرفوض أي وحدات، وتُبلغ الترويسات عن الرصيد المتبقي الفعلي.\n\n**الحدود الشهرية.** تُحتسب عمليات الالتقاط والمقارنات والتنقيحات والتوثيقات على المفاتيح الفعلية ضمن حد أقصى شهري لكل حساب (الشهر التقويمي بتوقيت UTC). تحمل الاستجابة المقبولة الترويسات `X-Quota-*`. يؤدي بلوغ الحد الأقصى إلى إعادة 402 QUOTA_EXCEEDED مع `error.details` `{ reason, used, limit, max_limit, cap, org_member }`، حيث تكون `cap` إحدى القيم `plan_max` أو `customer` أو `member_owner` أو `org`. وما دام الاشتراك الفعلي متأخر السداد أو غير مدفوع، تُعيد الطلبات القابلة للفوترة 402 PAYMENT_REQUIRED؛ وبالنسبة إلى مثل هذا الحساب، تُرفض عمليات الالتقاط (الفردية والدفعية) بهذا الخطأ بصرف النظر عن بيئة المفتاح، بما في ذلك مفاتيح الاختبار.\n\n**عمليات الالتقاط الجارية ووضع الاختبار.** لا يمكن لكل حساب أن يشغّل في الوقت نفسه سوى عدد محدود من عمليات الالتقاط (developer 10، startup 25، growth 100، legal_solo وlegal_seats 25، custom 200، website_archive 25، sandbox 5). فوق هذا الحد، تُعيد الواجهة 429 CONCURRENT_CAPTURE_LIMIT؛ وتبدأ الدفعة عناوين URL التي يتسع لها الحد وتسرد البقية في `not_admitted` (لكلٍّ منها `code` بالقيمة CONCURRENT_CAPTURE_LIMIT، إضافةً إلى `in_flight_limit`)، فأرسلها مجددًا لاحقًا. يقتصر العمل في وضع الاختبار (مفتاح اختبار، أو جلسة من دون فوترة مرتبطة) على 200 وحدة لكل حساب في اليوم بتوقيت UTC (429 TEST_MODE_DAILY_LIMIT؛ الترويسات `X-Test-Daily-*`)، ولا تُرسَل أي عملية التقاط في وضع الاختبار أبدًا إلى Internet Archive (`external_archive.skip_reason` \"test_mode\")."
    },
    {
      "name": "المقارنة والتنقيح",
      "description": "اشتقّ حزمة مختومة جديدة من عمليات التقاط موجودة: مقارنة بين عمليتي التقاط، أو نسخة منقّحة من إحداهما."
    },
    {
      "name": "توثيق البصمة",
      "description": "وثّق بصمات SHA-256 لملفات تحتفظ بها لديك. تُرسَل البصمات فقط؛ ولا تصل الملفات نفسها إلى ProofSnap أبدًا."
    },
    {
      "name": "عمليات الالتقاط المجدولة",
      "description": "عمليات التقاط متكررة (interval أو cron) ومراقبة التغييرات (on_change). كل تشغيل يُجري عملية التقاط هو عملية التقاط عادية: يُحتسب في الاستهلاك ويُعدّ ضمن الحدود الشهرية."
    },
    {
      "name": "القضايا",
      "description": "مساحات عمل للقضايا القانونية مع سجل حيازة مترابط بسلسلة من البصمات. متاحة في خطط Legal (legal_solo، legal_seats)؛ وتتلقى الخطط الأخرى 403 MATTERS_NOT_ENABLED، مع استثناءين. يجوز لمالك شركة انتهى اشتراكها أن يواصل سرد قضايا تلك الشركة وفتحها وتصديرها حتى تاريخ حذف بيانات الشركة (data_delete_after)؛ ويقتصر هذا الوصول على القراءة والتصدير، وعندها تضيف GET /matters القيمة read_only: true. ويجوز لحساب على خطة Archive only أن يسرد قضاياه ويفتحها ويصدّرها. لا يجوز إلا لمالك القضية إعادة تسميتها أو أرشفتها أو إدارة أعضائها أو ختمها أو مشاركتها أو تصديرها. مالك القضية هو من أنشأها. في قضية تابعة لشركة، لا يصبح مالك الشركة مالكًا للقضية إلا بعد أن يغادر منشئها الشركة. ومالك الشركة الذي أُضيف بصفة عضو لا يملك إلا حقوق العضو. يتلقى الأعضاء 403 FORBIDDEN عند هذه العمليات. تتطلب كل عملية كتابة على قضية (الإنشاء، وPATCH، وإسناد عمليات الالتقاط، والمشاركة وإلغاؤها، والختم، والتصدير) عنوان بريد إلكتروني مؤكَّدًا (403 EMAIL_NOT_VERIFIED، لجلسة لوحة التحكم ولمفتاح API لحساب لم يُؤكَّد عنوانه)، وبالنسبة إلى المستدعي الفعلي (مفتاح API فعلي، أو جلسة مع فوترة مرتبطة) تُرفض بالخطأ 402 PAYMENT_REQUIRED ما دام الاشتراك (أو اشتراك الشركة) متأخر السداد أو غير مدفوع. ترفض القضية المؤرشفة إعادة التسمية والأعضاء الجدد وعمليات الالتقاط الجديدة والمشاركة والتصدير (409 MATTER_ARCHIVED) إلى أن يُعاد فتحها؛ ويجوز للمستدعي الذي لا يستطيع إعادة فتحها (مالك شركة انتهى اشتراكها، أو حساب Archive only) أن يصدّرها مع ذلك."
    },
    {
      "name": "Webhooks",
      "description": "يرسل ProofSnap حدثًا بصيغة JSON عبر طلب POST إلى `webhook_url` الذي تمرّره مع عملية التقاط أو توثيق أو تنقيح أو جدول. يجب أن يستخدم عنوان URL بروتوكول https، وألّا يتضمن بيانات اعتماد (`user:password@`، 400 INVALID_URL)، وألّا يشير إلى عنوان خاص أو عنوان استرجاع (loopback) أو link-local أو carrier-grade NAT أو multicast أو عنوان محجوز أو عنوان بيانات وصفية سحابية، بما في ذلك صيغ IPv6 التي تتضمن مثل هذا العنوان من نوع IPv4. يُفحص ذلك عند قبول الطلب (422 SSRF_BLOCKED) ومرة أخرى عند كل تسليم: يُحلَّل اسم المضيف، ويُفحص كل عنوان، ولا يتصل التسليم إلا بعنوان اجتاز الفحص، فلا يُتواصل مع مضيف يُحلَّل لاحقًا إلى عنوان محظور. وإذا تعذّر تحليل اسم المضيف، لا يُرسَل التسليم. في كل المسارات، يُعامَل webhook_url الفارغ أو المكوّن من مسافات فقط على أنه عدم وجود webhook. لا تُرسل /compare أي webhooks.\n\n**اشتراكات الحساب.** بدلًا من webhook_url في كل طلب (أو إضافةً إليه)، اشترك بنقطة نهاية (endpoint) في أحداث الحساب عبر `POST /webhook-subscriptions`: يصل إليها كل حدث مطابق في الحساب، أيًّا كان المفتاح أو الجدول أو جلسة لوحة التحكم التي تسببت فيه. لكل اشتراك سرّه الخاص (يُعاد مرة واحدة عند إنشائه)، ويسرد أنواع الأحداث التي يريدها، ويمكنه التصفية حسب `host` أو `schedule_id`؛ يتلقى الاشتراك الفعلي أحداث عمليات الالتقاط الفعلية فقط، ويتلقى اشتراك الاختبار أحداث عمليات الالتقاط الاختبارية فقط. يسرد مفتاح API ويغيّر اشتراكات بيئته فقط؛ أما جلسة لوحة التحكم فترى جميع الاشتراكات. تحمل تسليمات الاشتراك `subscription_id` في المستوى الأعلى ولا تحمل أي رابط موقّع مسبقًا (تُحذف package_url وpackage_url_expires_at وupgraded_proof_url وcompare.package_url؛ ويحمل monitor.change_detected الحقل compare.compare_id): اجلب الحزمة عبر GET /captures/{capture_id}. تحمل أحداث عمليات الالتقاط أيضًا `capture_id` في المستوى الأعلى. يُشتق webhook-id لتسليم الاشتراك من الاشتراك وعملية الالتقاط (أو الجدول) والحدث. تنطبق قواعد التسليم وإعادة المحاولة الواردة أدناه. إضافةً إلى ذلك، تؤدي الاستجابة 410 إلى تعطيل الاشتراك فورًا (disabled_reason gone)، وتؤدي التسليمات التي تفشل لمدة 5 أيام دون أي نجاح (مع دورة كاملة واحدة على الأقل من محاولات الإعادة الفاشلة) إلى تعطيله (failing)، ويؤدي إبطال مفتاح API الذي أنشأه إلى تعطيله (key_revoked)؛ ويعيد PATCH مع status \"active\" تفعيله. يُحتفظ أيضًا بكل حدث لمدة 30 يومًا في `GET /events`.\n\n**الترويسات**\n\nيُوقَّع كل تسليم وفق مواصفة [Standard Webhooks](https://www.standardwebhooks.com/)، لذا يمكن لأي مكتبة رسمية من مكتبات Standard Webhooks التحقق منه:\n\n| الترويسة | القيمة |\n| --- | --- |\n| `Content-Type` | `application/json` |\n| `webhook-id` | `msg_<delivery id>`: هو نفسه للحدث نفسه من عملية الالتقاط نفسها، لذا يحمل التسليم المكرر المعرّف نفسه. استخدمه لإزالة التكرار: عالج كل webhook-id مرة واحدة. تُعيد `GET /webhook-events/{capture_id}` المعرّف نفسه دون البادئة `msg_` بوصفه `delivery_id` |\n| `webhook-timestamp` | وقت Unix بالثواني لحظة إرسال التسليم. يشمله التوقيع |\n| `webhook-signature` | `v1,<base64>`: قيمة HMAC-SHA256 محسوبة على `<webhook-id>.<webhook-timestamp>.<raw body>`. أثناء تدوير سر webhook يحمل إدخالين تفصل بينهما مسافة، `v1,<new> v1,<previous>`؛ اقبل التسليم عندما يتطابق أي إدخال منهما |\n| `User-Agent` | `ProofSnap-Webhook/2.0` |\n\n**التحقق من التسليم.** تحقّق اعتمادًا على البايتات نفسها التي استلمتها تمامًا، قبل تحليلها (parsing). ارفض التسليم الذي يختلف فيه webhook-timestamp عن ساعتك بأكثر من 5 دقائق (تفعل المكتبات الرسمية ذلك)، وأزل التكرار حسب webhook-id. مفتاح التوقيع هو جزء السر الواقع بعد `whsec_`، بعد فك ترميزه من base64. تتكون `webhook_secret` من `whsec_` متبوعة بترميز base64 قياسي، وهي الصيغة التي تتوقعها المكتبات الرسمية، فمرّرها إليها كما هي.\n\n```js\n// npm install standardwebhooks\nimport { Webhook } from \"standardwebhooks\";\nconst wh = new Webhook(process.env.PROOFSNAP_WEBHOOK_SECRET); // whsec_... (webhook_secret)\n// rawBody: the request body as received (string or Buffer), headers: the request headers\nconst event = wh.verify(rawBody, headers); // throws on a bad signature or a timestamp outside 5 minutes\n```\n\nمن دون مكتبة (Node):\n\n```js\nconst key = Buffer.from(secret.slice(\"whsec_\".length), \"base64\");\nconst signed = `${headers[\"webhook-id\"]}.${headers[\"webhook-timestamp\"]}.${rawBody}`;\nconst expected = crypto.createHmac(\"sha256\", key).update(signed).digest(\"base64\");\nconst ok = headers[\"webhook-signature\"].split(\" \").some((part) => {\n  const [version, sig] = part.split(\",\");\n  return version === \"v1\" && sig && sig.length === expected.length\n    && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));\n});\n// and: Math.abs(Date.now() / 1000 - Number(headers[\"webhook-timestamp\"])) <= 300\n```\n\n**سر webhook.** لكل مفتاح API سرّه الخاص (`whsec_...`). يُعرض مرة واحدة، عند إنشاء المفتاح في لوحة التحكم ضمن «المطورون» (الحقل `webhook_secret` في الاستجابة التي تُنشئ المفتاح). لا يمكن قراءته لاحقًا: لا تُعيده قائمة المفاتيح أبدًا. إذا فقدته، أو ربما تسرّب، فأنشئ سرًا جديدًا في لوحة التحكم ضمن «المطورون» (الإجراء «سر webhook جديد» على المفتاح). يُعرض السر الجديد أيضًا مرة واحدة فقط. **تداخل التدوير:** لمدة 24 ساعة بعد التدوير (حتى `previous_secret_expires_at` في استجابة التدوير) يُوقَّع كل تسليم بالسر الجديد والسر السابق معًا (إدخالان `v1,` في webhook-signature)، لتتمكن من نقل نقطة النهاية لديك إلى السر الجديد دون فقدان أي تسليمات. يُرفض التدوير الثاني أثناء سريان ذلك التداخل بالخطأ 409 WEBHOOK_SECRET_ROTATION_PENDING مع `error.details.previous_secret_expires_at` (موعد إمكانية التدوير مجددًا)، فلا يكون أكثر من سرّين نشطين في آن واحد أبدًا، ولا يُسحب مبكرًا أبدًا سرٌّ قد يظل المستلمون يعتمدون عليه. أما إذا تسرّب السر، فأبطل المفتاح وأنشئ مفتاحًا جديدًا. يؤدي إبطال المفتاح إلى حذف السرّين كليهما. تستخدم التسليمات أسرار المفتاح لحظة إرسالها، بما في ذلك تسليمات العمل الذي بدأ قبل التدوير. الطلب الصادر من جلسة لوحة تحكم مسجَّل الدخول فيها لا يملك مفتاح API، وبالتالي لا يملك سر webhook، لذا يُرفض `webhook_url` فيه بالخطأ 400 `WEBHOOK_REQUIRES_API_KEY` (عمليات الالتقاط، والدفعات، والتنقيحات، وتوثيقات البصمات، وإنشاء عملية التقاط مجدولة أو تعديلها). تستخدم التشغيلات المجدولة سر المفتاح الذي أنشأ الجدول. وإذا كان المفتاح قد أُبطل، أو تعذّرت قراءة سرّه لحظة التسليم، لا يُرسَل الحدث وتُسجَّل المحاولة مع السبب (اقرأه عبر `GET /webhook-events/{capture_id}`).\n\n**التسليم.** لكل محاولة مهلة مدتها 10 ثوانٍ. لا تُتبَع عمليات إعادة التوجيه أبدًا: تُعدّ الاستجابة 3xx فشلًا، شأنها شأن الاستجابة خارج النطاق 2xx أو انتهاء المهلة. لا يُقرأ جسم الاستجابة. **إعادة المحاولة:** التسليم الذي تنتهي مهلته، أو يتعذر اتصاله، أو يتلقى استجابة خارج النطاق 2xx (باستثناء 410 Gone) يُرسَل مجددًا بعد نحو 5 دقائق و30 دقيقة وساعتين و8 ساعات و24 ساعة (المحاولات من 2 إلى 6، نحو 35 ساعة إجمالًا)، وفي كل مرة بالقيمة نفسها لـ `webhook-id` وبقيمة جديدة لـ `webhook-timestamp`. بعد ذلك يُتخلّى عن التسليم. يحمل التسليم المعاد الحمولة نفسها من دون رابط التنزيل الصالح 24 ساعة (`package_url`، `package_url_expires_at`): اجلب الحزمة عبر GET /captures/{capture_id}. تُسقَط تسليمات عملية الالتقاط التي مُحيت في الأثناء. ويُعاد التسليم بالطريقة نفسها عند فشل عابر في قراءة سر التوقيع من جانبنا. لا يُعاد التسليم في حالة 410 Gone، أو إعادة التوجيه، أو العنوان المحظور، أو المفتاح المفقود أو المُبطَل. قد يختلف جسم المحاولة المعادة عن المحاولة الأولى (دون رابط تنزيل): أزل التكرار حسب webhook-id، لا حسب بصمة الجسم. استجب بالرمز 2xx بسرعة. يسجّل سجل المحاولات فئة خطأ عامة (connection_failed، timeout، blocked_address، redirect_not_followed، http_<status>)، ولا يسجّل أبدًا خطأ الشبكة الخام. يُرسَل certify.completed وredaction.completed بشكل غير متزامن، بعد إعادة الاستجابة بوقت قصير. تُسجَّل كل محاولة؛ اقرأ السجل عبر `GET /webhook-events/{capture_id}`.\n\n**الأحداث**\n\n`capture.completed`: اكتملت عملية التقاط (فردية، أو عنصر في دفعة، أو تشغيل لجدول من نوع interval/cron). تتلقاه أيضًا اشتراكات webhook الخاصة بالحساب لعملية التقاط خط الأساس ولعمليات التقاط التغيير في جدول on_change (أما webhook_url الخاص بالجدول نفسه فيتلقى لها الحدث monitor.change_detected). تحمل `capture` الحقول نفسها التي تحملها استجابة `GET /captures/{capture_id}` لعملية التقاط مكتملة، إضافةً إلى `timestamps.eidas` عند إضافة طابع زمني مؤهل، و`schedule_id` (null خارج الجدول)، و`test_mode` و`completed_at`.\n\n```json\n{ \"event\": \"capture.completed\",\n  \"capture\": { \"capture_id\": \"cap_...\", \"status\": \"completed\", \"url\": \"https://example.com\", \"schedule_id\": null, \"test_mode\": false, \"captured_at\": \"...\", \"completed_at\": \"...\",\n    \"package_url\": \"https://...\", \"package_url_expires_at\": \"...\", \"verification_url\": \"https://...\",\n    \"manifest_hash\": \"sha256:...\", \"timestamps\": { \"bitcoin_ots\": { \"status\": \"pending_confirmation\", \"block_height\": null } },\n    \"c2pa\": { \"included\": true, \"signed_screenshot_sha256\": \"sha256:...\" }, \"file_count\": 21, \"size_bytes\": 1234567, \"metadata\": {} } }\n```\n\n`capture.failed`: تعذّر إكمال عملية الالتقاط. يحمل `error.message` سببًا عامًا موجزًا (في حالة الفشل غير المتوقع \"The capture could not be completed.\")؛ ولا تُرسَل أبدًا تفاصيل الخطأ الداخلية. ويشمل ذلك التشغيل الذي تعطّل أو انتهت مهلته: تَسِمه عملية مسح دورية بأنه failed بعد نحو 30 دقيقة من بدئه، ويُرسَل capture.failed حينها (الرسالة \"Capture did not finish (timed out).\"). وترسل عملية المسح نفسها certify.failed أو redaction.failed لتوثيق بصمة أو تنقيح لم يكتمل. والتشغيل الذي ينتهي بعد الإبلاغ عن فشله لا يُفوتَر ولا يرسل capture.completed.\n\n```json\n{ \"event\": \"capture.failed\",\n  \"capture\": { \"capture_id\": \"cap_...\", \"status\": \"failed\", \"url\": \"https://example.com\", \"schedule_id\": null, \"test_mode\": false, \"failed_at\": \"...\",\n    \"error\": { \"code\": \"CAPTURE_FAILED\", \"message\": \"...\" }, \"metadata\": {} } }\n```\n\n`certify.completed`: اكتمل توثيق بصمة. تحمل `certificate` الحقول نفسها التي تحملها الاستجابة المتزامنة لـ `POST /certify-hash`. يُسلَّم بشكل غير متزامن، بعد إعادة الاستجابة بوقت قصير.\n\n`certify.failed`: تعذّر إكمال توثيق بصمة.\n\n```json\n{ \"event\": \"certify.failed\",\n  \"certificate\": { \"certify_id\": \"cert_...\", \"status\": \"failed\", \"failed_at\": \"...\",\n    \"error\": { \"code\": \"CERTIFY_FAILED\", \"message\": \"The certification could not be completed. Retry the request.\" } } }\n```\n\n`redaction.completed`: اكتمل تنقيح. تحمل `redaction` الحقول نفسها التي تحملها استجابة `POST /captures/{capture_id}/redact`.\n\n`redaction.failed`: تعذّر إكمال تنقيح. يحمل `error.code` الرمز الذي كان الطلب سيُعيده (مثل REDACTION_LEAK_DETECTED)، مع `details` عند وجودها؛ ويحمل التنقيح الذي وسمته عملية المسح بأنه فاشل الرمز REDACTION_FAILED.\n\n```json\n{ \"event\": \"redaction.failed\",\n  \"redaction\": { \"redaction_id\": \"red_...\", \"status\": \"failed\", \"redacted_from\": \"cap_...\", \"failed_at\": \"...\",\n    \"error\": { \"code\": \"REDACTION_LEAK_DETECTED\", \"message\": \"...\", \"details\": { \"files\": [\"page.html\"], \"rule_indexes\": [0], \"metadata_fields\": [], \"in_capture_url\": false } } } }\n```\n\n`monitor.change_detected`: رصد جدول من نوع `on_change` تغيّرًا في نص الصفحة وأجرى عملية التقاط جديدة.\n\n```json\n{ \"event\": \"monitor.change_detected\", \"schedule_id\": \"sch_...\",\n  \"before_capture_id\": \"cap_...\", \"after_capture_id\": \"cap_...\", \"url\": \"https://example.com\",\n  \"compare\": { \"package_url\": \"https://...\", \"diff_summary\": { } },\n  \"diff_status\": \"ok\", \"verdict\": \"changed\", \"text_verdict\": \"changed\", \"images_verdict\": \"unchanged\", \"metadata_verdict\": \"unchanged\",\n  \"detected_at\": \"...\", \"test_mode\": false }\n```\n\nتعكس `verdict` و`text_verdict` و`images_verdict` و`metadata_verdict` (منذ 2026-10-14) نتائج المقارنة لكل فئة في compare.diff_summary (null عند عدم وجود مقارنة). لا يرسل الجدول المضبوط على `alert_on: \"text\"` الحدث monitor.change_detected إلا إذا كانت قيمة text_verdict هي changed (أو عند عدم وجود نتيجة مقارنة).\n\n`capture.anchor_confirmed` (الاشتراكات وGET /events فقط): تأكّد ربط إثبات OpenTimestamps الخاص بالحزمة بسلسلة كتل Bitcoin، وعادةً ما يحدث ذلك بعد بضع ساعات من عملية الالتقاط.\n\n```json\n{ \"event\": \"capture.anchor_confirmed\", \"capture_id\": \"cap_...\", \"schedule_id\": null, \"url\": \"https://example.com\",\n  \"confirmed_at\": \"...\", \"bitcoin_block_height\": 968549, \"upgraded_proof_available\": true, \"test_mode\": false }\n```\n\n`schedule.paused` (الاشتراكات وGET /events فقط): أوقف ProofSnap جدولًا مؤقتًا. تكون `reason` إحدى القيم `no_billing` (لا وصول إلى API)، أو `plan_downgrade` (تسمح الخطة بعدد أقل من الجداول، أو لا تسمح بـ on_change، أو لا تسمح بعمل جديد)، أو `failures` (خمسة تشغيلات فاشلة متتالية)، أو `key_revoked` أو `invalid_cron`. الإيقاف المؤقت الذي تجريه بنفسك ليس حدثًا.\n\n```json\n{ \"event\": \"schedule.paused\", \"schedule_id\": \"sch_...\", \"url\": \"https://example.com\", \"reason\": \"failures\",\n  \"paused_at\": \"...\", \"test_mode\": false }\n```\n\nتكون `diff_status` إحدى القيم `ok` (تحمل compare المقارنة)، أو `no_baseline` (لا توجد عملية التقاط سابقة للمقارنة بها)، أو `failed` (فشلت المقارنة، وقيمة compare هي null)."
    },
    {
      "name": "عام",
      "description": "عمليات لا تحتاج إلى مفتاح API."
    },
    {
      "name": "استيراد الحزم",
      "description": "استورد إلى حسابك حزم أدلة ProofSnap الموجودة (ملفات ZIP أنشأتها إضافة ProofSnap: عمليات التقاط الويب، والجلسات متعددة علامات التبويب، وتوثيقات الملفات الفردية والمجمّعة؛ أو أنشأتها هذه الواجهة: عمليات الالتقاط، والتوثيقات، والمقارنات، والتنقيحات)، لتُعامَل معاملة عمليات الالتقاط التي تجريها عبر API: يُحتفظ بها لمدة 10 سنوات، وتتاح للحفظ القانوني والقضايا والتصدير.\n\nلخطط Legal فقط (legal_solo، legal_seats)، باستخدام مفتاح API فعلي أو جلسة لوحة تحكم مع فوترة مرتبطة (يتلقى مفتاح الاختبار والجلسة دون فوترة ووضع الاختبار (sandbox) الخطأ 403 IMPORT_NOT_ALLOWED). يتلقى عنوان البريد الإلكتروني غير المؤكَّد 403 EMAIL_NOT_VERIFIED، والاشتراك المتأخر السداد 402 PAYMENT_REQUIRED. الاستيراد ليس عملية التقاط: لا يُحتسب ضمن الحدود الشهرية ولا رسوم عليه لكل استيراد؛ ويُفوتَر بوصفه بيانات مخزنة فقط.\n\n**سير العمل.** 1. `POST /imports` مع اسم الملف وحجمه الدقيق بالبايت (2 GiB كحد أقصى): تحتوي الاستجابة على عنوان URL موقّع مسبقًا لكل جزء حجمه 32 MiB (الجزء الأخير أصغر)، صالح لمدة ساعة واحدة. 2. أرسل بايتات كل جزء عبر PUT إلى عنوانه، واحتفظ بترويسة الاستجابة `ETag` لكل جزء. 3. `POST /imports/{import_id}/complete` مع رقم كل جزء وقيمة ETag الخاصة به. 4. استعلم دوريًا عبر `GET /imports/{import_id}` حتى تصبح الحالة نهائية.\n\n**الحالات.** `uploading` (بانتظار رفع الأجزاء واستدعاء complete؛ يصبح الرفع الذي لا يكتمل خلال 24 ساعة `expired`)، و`verifying` (يجري فحص الحزمة)، و`accepted` (اجتازت التحقق: `capture_id` هو عملية الالتقاط الجديدة، وهو المعرّف نفسه للاستيراد)، و`rejected` (لم تجتز التحقق: تسرد `reasons` الرموز؛ ولا يُحتفظ بشيء سوى سجل الاستيراد)، و`duplicate` (يحتوي حسابك بالفعل على عملية التقاط لها البيان نفسه: `duplicate_of`)، و`expired`.\n\n**التحقق.** صارم: توقيع RSA على manifest.json، ووجود كل ملف مُدرج ببصمة SHA-256 المُدرجة (وببصمة SHA-512 والحجم حيثما أُدرجا)، وعدم وجود ملفات لا يُدرجها البيان (باستثناء الملفات التي يضيفها نظام التشغيل، مثل .DS_Store، والإثباتات المشتقة من البيان)، ووجود ملف واحد على الأقل محسوبة بصمته، ومعرّف دليل متّسق، وإعادة حساب سلسلة البصمات في سجل التحليل الجنائي الرقمي (forensic log)، وإثبات OpenTimestamps مقابل البيان (دون اتصال بالإنترنت)، والطابع الزمني المؤهل وفق eIDAS مقابل جذر Disig المثبّت مع بيانات التحقق الموجودة في الحزمة. تُفحص بنية ZIP قبل قراءة أي شيء (لا تشفير، ولا ZIP64، ولا روابط، وأسماء آمنة، ولا أسماء مكررة، والأحجام وCRC-32 لكل إدخال). تُرفض حزمة الاختبار الصادرة عن الواجهة (التي تحمل علامة مائية). تُقبل حزم API التي خُتمت قبل أن تُحسب بصمة evidence.pdf، مع إدراج ذلك الملف في `verification.checks.files.unhashed`.\n\n**المستويات** (`summary`): `integrity` (verified أو failed)، و`time_anchor` (eidas_qualified، eidas_qualified_revocation_unknown، ots_bitcoin_attested_unconfirmed، ots_pending، none) و`provenance`: لحزم API إما `issued_by_proofsnap` (البيان موجود في السجل العام للحزم الصادرة عن الواجهة) أو `unverified`؛ ولحزم الإضافة إما `registered_by_extension_account` (سجّل مستخدم للإضافة مسجَّل الدخول ذلك البيان عند إنشائه) أو `not_registered`؛ و`unknown` عند تعذّر إجراء البحث. لا يؤدي مستوى provenance أبدًا إلى إفشال الاستيراد.\n\n**عملية الالتقاط.** الاستيراد المقبول هو عملية التقاط قيمة `source` فيها \"import\": يمكن تنزيلها (ملف ZIP المرفوع بايتًا ببايت) والتحقق منها (تُعيد POST /captures/{capture_id}/verify إجراء الفحوص نفسها)، ويمكن وضعها تحت الحفظ القانوني ونقلها إلى سلة المهملات ومحوها، وتظهر في القضايا (عند استيرادها مع `matter_id`، يسجّل سجل الحيازة للقضية `package.imported`) وفي عمليات التصدير، ويُحتفظ بها 10 سنوات من تاريخ الاستيراد. قيمة `captured_at` فيها هي الوقت الذي تذكره الحزمة نفسها (`import.captured_at_basis` \"package_claim\")، وليست أبدًا وقتًا رصده ProofSnap؛ أما `created_at` فهو وقت إنشاء عملية الرفع. تُرفض المقارنات والتنقيحات لحزمة مستوردة (422 IMPORT_NOT_SUPPORTED).\n\n**الحدود.** 3 عمليات استيراد جارية كحد أقصى (uploading أو verifying) لكل حساب (409 IMPORT_LIMIT)، و50 عملية استيراد تُنشأ لكل حساب في اليوم بتوقيت UTC (429 RATE_LIMITED، `details.action` \"import_create\"؛ يُحتسب كل طلب إنشاء، بما في ذلك الطلب الذي يُرفض لاحقًا)، وحد المعدّل في الدقيقة الخاص بالخطة على create وcomplete. حجم الاستيراد: 5 GiB كحد أقصى في اليوم بتوقيت UTC و20 GiB في الشهر بتوقيت UTC من أحجام الحزم المُعلنة لكل حساب، تُحتسب عند create (وعند complete للرفع الأقدم)؛ وعند تجاوزه يُعاد 429 IMPORT_DAILY_BYTES_LIMIT أو IMPORT_MONTHLY_BYTES_LIMIT مع `Retry-After`؛ ولا يُحتسب الرفع الملغى."
    }
  ],
  "paths": {
    "/captures": {
      "post": {
        "tags": [
          "عمليات الالتقاط"
        ],
        "operationId": "createCapture",
        "summary": "التقاط عنوان URL",
        "description": "يقبل عملية الالتقاط ويعيد 202 فورًا؛ وتُنفَّذ عملية الالتقاط في الخلفية. استعلم دوريًا عبر `GET /captures/{capture_id}` أو مرِّر `options.webhook_url`. يُحلَّل اسم المضيف في عنوان URL ويُفحص قبل قبول عملية الالتقاط.\n\nيُطبَّق حد المعدّل على كل حساب ضمن نوافذ زمنية مدتها 60 ثانية (تتشارك جميع مفاتيح API في الحساب وجلسة لوحة التحكم الخاصة به نافذة زمنية واحدة). بعد أن يعمل مُحدِّد المعدّل، تتضمن الاستجابات من هذا المسار الترويستين `RateLimit-Policy` و `RateLimit` (استنادًا إلى مسودة IETF draft-ietf-httpapi-ratelimit-headers) إضافةً إلى الترويسات القديمة `X-RateLimit-Limit` و `X-RateLimit-Remaining` و `X-RateLimit-Reset`. وتضيف الاستجابة 429 الترويسة `Retry-After`.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaptureRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "تم القبول. إعادة إرسال طلب سابق بمفتاح Idempotency-Key نفسه (إعادة آمنة دون تكرار) تعيد أيضًا 202، مع `Idempotent-Replay: true` وجسم الاستجابة الأصلي.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/QuotaLimit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/QuotaRemaining"
              },
              "X-Quota-Used": {
                "$ref": "#/components/headers/QuotaUsed"
              },
              "X-Quota-Cap": {
                "$ref": "#/components/headers/QuotaCap"
              },
              "X-Quota-Org-Limit": {
                "$ref": "#/components/headers/QuotaOrgLimit"
              },
              "X-Quota-Org-Used": {
                "$ref": "#/components/headers/QuotaOrgUsed"
              },
              "X-Sandbox-Limit": {
                "$ref": "#/components/headers/SandboxLimit"
              },
              "X-Sandbox-Remaining": {
                "$ref": "#/components/headers/SandboxRemaining"
              },
              "X-Sandbox-Used": {
                "$ref": "#/components/headers/SandboxUsed"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaptureAccepted"
                },
                "example": {
                  "capture_id": "cap_a1B2c3D4e5F6g7H8",
                  "status": "processing",
                  "url": "https://example.com",
                  "poll_path": "/v1/captures/cap_a1B2c3D4e5F6g7H8",
                  "message": "Capture accepted. Poll GET /v1/captures/{capture_id} (or use webhook_url) for completion.",
                  "metadata": {}
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST أو MISSING_FIELD أو INVALID_URL أو INVALID_TIMESTAMP_TYPE أو INVALID_VIEWPORT أو METADATA_TOO_LARGE أو INVALID_EVIDENCE_PACK_OPTIONS. WEBHOOK_REQUIRES_API_KEY: أُرسل webhook_url من جلسة لوحة التحكم (تتطلب webhooks مفتاح API).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED (بلوغ الحد الأقصى الشهري أو الحصة المسموح بها في وضع الاختبار (sandbox)؛ وتحمل الاستجابة الترويسات X-Quota-* أو X-Sandbox-*) أو PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "EIDAS_REQUIRES_LIVE_KEY: include_eidas مع مفتاح اختبار (مفتاح ps_live_ لحساب غير مرتبط بحساب فوترة يعمل كمفتاح اختبار). EMAIL_NOT_VERIFIED: جلسة لوحة تحكم لم يُؤكَّد عنوان بريدها الإلكتروني. PLAN_ARCHIVE_ONLY: الخطة تحتفظ بالأرشيف فقط ولا تُنشئ أي عمل جديد. PLAN_API_READ_ONLY: مفتاح API لحساب Website Archive (مفتاحه للقراءة فقط؛ استخدم لوحة التحكم). PLAN_API_READ_ONLY مع details.reason \"session_outside_dashboard\": جلسة لوحة تحكم لحساب Website Archive لا يحتوي طلبها على الترويسة Origin أو يحتوي على عنوان ليس من عناوين لوحة تحكم ProofSnap (يُنشأ العمل الجديد في هذه الخطة من لوحة التحكم فقط؛ وهذا قيد مرن وليس حدًّا أمنيًا).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "IDEMPOTENCY_IN_PROGRESS.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED أو TARGET_UNREACHABLE أو IDEMPOTENCY_KEY_REUSED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: طلبات كثيرة جدًا في النافذة الزمنية الحالية البالغة 60 ثانية. يكرر `error.details.retry_after_seconds` قيمة Retry-After. وتُبلغ RateLimit عن r=0 وعدد الثواني حتى إعادة ضبط النافذة. ويُعاد RATE_LIMITED أيضًا لجلسة لوحة تحكم في Website Archive عند بلوغ حدها اليومي البالغ 100 وحدة في اليوم بتوقيت UTC، وهو حد مشترك بين عمليات الالتقاط المفردة والدفعية (وحدة واحدة لكل عنوان URL) و compare و redact و certify-hash (وحدة واحدة لكل طلب)؛ ويُرفض الطلب الذي لا يتسع له الرصيد المتبقي بالكامل (details.daily_limit و details.remaining و details.action \"wa_session_captures\"؛ ويعطي Retry-After و details.retry_after_seconds عدد الثواني حتى اليوم التالي بتوقيت UTC، ولا تُرسل ترويسات RateLimit و X-RateLimit-* الخاصة بالدقيقة). أما الرفض القصير الذي لا يحمل سوى details.retry_after_seconds و Retry-After: 1 فيعني تحديثًا متزامنًا للعدّاد: أعد المحاولة. والرفض الذي لا يرجع إلى خطأ من المستدعي يعيد الوحدات؛ أما العمل المقبول بالرمز 202 الذي يفشل لاحقًا فتبقى وحدته محتسبة. وهذا قيد مرن وليس حدًّا أمنيًا. وعملية الالتقاط المفردة التي تُقدَّم استجابتها لمفتاح Idempotency-Key من المخزن (إعادة، أو 409 أو 422) تُعالَج قبل الحد اليومي ولا تستهلك أي وحدة.\n\nCONCURRENT_CAPTURE_LIMIT: عدد كبير جدًا من عمليات الالتقاط الخاصة بالحساب قيد التشغيل في الوقت نفسه (developer 10، startup 25، growth 100، legal_solo و legal_seats 25، custom 200، website_archive 25، sandbox 5)؛ وتبيّن `details.limit` و `details.in_flight` و `details.requested` و Retry-After متى تعيد المحاولة.\n\nTEST_MODE_DAILY_LIMIT: العمل في وضع الاختبار (مفتاح اختبار، أو جلسة دون فوترة مرتبطة) محدود بـ 200 وحدة لكل حساب في اليوم بتوقيت UTC؛ `details.used` و `details.limit` و `details.reset_at` والترويسات `X-Test-Daily-*` و Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "CAPTURE_FAILED (تعذّر بدء عملية الالتقاط؛ details.capture_id) أو INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        }
      },
      "get": {
        "tags": [
          "عمليات الالتقاط"
        ],
        "operationId": "listCaptures",
        "summary": "عرض قائمة عمليات الالتقاط الخاصة بك",
        "description": "السجلات التي أنشأتها، الأحدث أولًا، بجميع الحالات (بما فيها المحذوفة، أي سلة المهملات؛ صفِّ النتائج باستخدام status). يحمل كل صف الحقول source و redacted_from و label و compared، لذا يمكن عرض نوعه دون قراءته مرة أخرى. ولا تظهر عملية الالتقاط التي أُجريت لصالح شركة في القائمة إلا ما دام بإمكانك التصرف فيها (تشغل مقعدًا في تلك الشركة أو تملكها)؛ ولذلك قد تحتوي الصفحة على صفوف أقل من limit، ويتابع next_cursor بعد آخر صف مقروء. وتُدرج الشهادات والمقارنات والنسخ المنقّحة.\n\nالبحث (في قائمتك فقط): تعثر المعاملات url أو host أو schedule_id، مع from / to و status، على سجل صفحة في يوم معيّن. ويؤدي عامل التصفية غير الصالح، أو تمرير اثنين من url / host / schedule_id، أو قيمة from غير السابقة لـ to، إلى 400 MALFORMED_REQUEST.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "حجم الصفحة، من 1 إلى 200 (الافتراضي 50). القيمة خارج هذا النطاق تعيد 400 MALFORMED_REQUEST.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "قيمة next_cursor من الصفحة السابقة (بالصيغة created_at|capture_id، أو completed_at|capture_id مع order=completed_at). مرِّرها دون تغيير.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "الصفوف ذات هذه الحالة فقط: processing أو completed أو failed أو deleted (سلة المهملات). أي قيمة أخرى تعيد 400 MALFORMED_REQUEST.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "description": "عمليات الالتقاط لعنوان URL هذا بالضبط فقط، كما التُقط (http أو https). واحد على الأكثر من url و host و schedule_id.",
            "schema": {
              "type": "string",
              "maxLength": 2048
            }
          },
          {
            "name": "host",
            "in": "query",
            "required": false,
            "description": "عمليات الالتقاط التي يقع عنوان URL الخاص بها على اسم المضيف هذا فقط (دون تمييز حالة الأحرف، مثل www.example.com؛ والنطاقات الفرعية مضيفون منفصلون). واحد على الأكثر من url و host و schedule_id. الصفوف المنشأة قبل 2026-10-05 تحمل المضيف بعد تشغيل الاستكمال الرجعي للبيانات (backfill).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "schedule_id",
            "in": "query",
            "required": false,
            "description": "عمليات الالتقاط التي أجراها هذا الجدول فقط. واحد على الأكثر من url و host و schedule_id.",
            "schema": {
              "type": "string",
              "pattern": "^sch_[A-Za-z0-9]{8,32}$"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "أُنشئ في هذه اللحظة أو بعدها: تاريخ (YYYY-MM-DD، منتصف الليل بتوقيت UTC) أو طابع زمني بتنسيق ISO 8601.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "أُنشئ قبل هذه اللحظة (غير شاملة لها): تاريخ (YYYY-MM-DD، منتصف الليل بتوقيت UTC) أو طابع زمني بتنسيق ISO 8601. يجب أن يكون لاحقًا لـ from. ومع schedule_id أو url، يكون أحدث صف قبل لحظة ما هو النسخة التي كانت سارية آنذاك.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "created_at (الافتراضي: الأحدث إنشاءً أولًا) أو completed_at (الأحدث اكتمالًا أولًا؛ فقط مع status=completed، وإلا 400 MALFORMED_REQUEST). مع completed_at، يحدّ from و to قيمة completed_at، ويكون next_cursor بالصيغة completed_at|capture_id. يعمل مع url و host و schedule_id.",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "completed_at"
              ],
              "default": "created_at"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "صفحة من عمليات الالتقاط.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "captures": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "capture_id": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string",
                            "nullable": true
                          },
                          "status": {
                            "type": "string",
                            "nullable": true
                          },
                          "created_at": {
                            "type": "string",
                            "nullable": true
                          },
                          "completed_at": {
                            "type": "string",
                            "nullable": true,
                            "description": "وقت اكتمال عملية الالتقاط (null أثناء المعالجة أو بعد الفشل)."
                          },
                          "batch_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "schedule_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "test_mode": {
                            "type": "boolean",
                            "nullable": true
                          },
                          "external_archive": {
                            "allOf": [
                              {
                                "$ref": "#/components/schemas/ExternalArchive"
                              }
                            ],
                            "description": "للصفوف المكتملة (completed) فقط. مماثل لـ external_archive في GET /captures/{capture_id}."
                          },
                          "label": {
                            "type": "string",
                            "nullable": true,
                            "description": "تسمية الصف (للشهادات: التسمية المعطاة؛ للنسخ المنقّحة: عملية الالتقاط التي تنقّحها)."
                          },
                          "compared": {
                            "type": "array",
                            "nullable": true,
                            "items": {
                              "type": "string"
                            },
                            "description": "للمقارنات فقط: معرّفا عمليتي الالتقاط المصدر."
                          },
                          "source": {
                            "type": "string",
                            "nullable": true,
                            "description": "طريقة إنشاء الصف: api (عملية التقاط ويب)، أو schedule، أو certify-hash (شهادة)، أو compare (مقارنة)، أو redact (نسخة منقّحة)؛ و null في الصفوف الأقدم من هذا الحقل."
                          },
                          "redacted_from": {
                            "type": "string",
                            "nullable": true,
                            "description": "للنسخ المنقّحة فقط: عملية الالتقاط التي أُنشئت منها النسخة المنقّحة."
                          },
                          "captured_at": {
                            "type": "string",
                            "nullable": true,
                            "format": "date-time",
                            "description": "وقت التقاط الصفحة (الصفوف المكتملة). وفي النسخة المنقّحة، هو وقت التقاط عملية الالتقاط المصدر (ويبيّن redacted_at وقت إنشاء النسخة المنقّحة)."
                          },
                          "redacted_at": {
                            "type": "string",
                            "format": "date-time",
                            "description": "للنسخ المنقّحة فقط: وقت إنشاء النسخة المنقّحة. وقيمة captured_at للنسخة المنقّحة هي وقت التقاط عملية الالتقاط المصدر."
                          },
                          "compared_at": {
                            "type": "string",
                            "nullable": true,
                            "format": "date-time",
                            "description": "للمقارنات فقط: وقت إجراء المقارنة. ويحمل captured_at الخاص بها القيمة نفسها (أُبقي عليه من أجل العملاء الأقدم)؛ استخدم compared_at."
                          },
                          "manifest_hash": {
                            "type": "string",
                            "nullable": true,
                            "description": "sha256:<hex> لملف manifest.json في الحزمة (الصفوف المكتملة)."
                          },
                          "private_note": {
                            "type": "string",
                            "nullable": true,
                            "description": "ملاحظتك الخاصة على عملية الالتقاط (انظر PrivateNote)، ولا تكون أبدًا جزءًا من حزمة الأدلة."
                          },
                          "legal_hold": {
                            "type": "boolean",
                            "description": "عملية الالتقاط خاضعة للحفظ القانوني (لا تُحذف ما دامت القيمة true؛ ويُرفع الحفظ تلقائيًا عند انتهاء الاشتراك ومرور 30 يومًا عليه). انظر PATCH /captures/{capture_id}."
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "description": "null عندما تحتوي هذه الصفحة على صفوف أقل من limit.",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: قيمة غير صالحة لـ cursor، أو status (إحدى القيم processing أو completed أو failed أو deleted)، أو limit (من 1 إلى 200)، أو عامل تصفية البحث (url و host و schedule_id و from و to؛ واحد على الأكثر من url / host / schedule_id؛ ويجب أن يكون from أسبق من to).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/batch": {
      "post": {
        "tags": [
          "عمليات الالتقاط"
        ],
        "operationId": "createBatch",
        "summary": "التقاط عدة عناوين URL",
        "description": "يبدأ عملية التقاط واحدة لكل عنوان URL بالخيارات نفسها. كل عملية التقاط مستقلة: استعلم عن كل capture_id، أو استقبل webhook واحدًا لكل عملية التقاط. يستهلك وحدة واحدة من حد المعدّل ووحدة واحدة من الحد الشهري لكل عنوان URL. والدفعة التي تحتوي على عناوين URL أكثر مما تسمح به نافذة زمنية واحدة لحد المعدّل لا يمكن أن تنجح أبدًا، ولذلك تُرفض بالرمز 400 RATE_LIMIT_COST_TOO_HIGH بدلًا من 429. قسّمها إلى طلبات أصغر. وإعادة إرسال دفعة مكتملة (بمفتاح Idempotency-Key نفسه والجسم نفسه) تُعالَج قبل تطبيق حد المعدّل ولا تستهلك أي وحدة منه.\n\nيُطبَّق حد المعدّل على كل حساب ضمن نوافذ زمنية مدتها 60 ثانية (تتشارك جميع مفاتيح API في الحساب وجلسة لوحة التحكم الخاصة به نافذة زمنية واحدة). بعد أن يعمل مُحدِّد المعدّل، تتضمن الاستجابات من هذا المسار الترويستين `RateLimit-Policy` و `RateLimit` (استنادًا إلى مسودة IETF draft-ietf-httpapi-ratelimit-headers) إضافةً إلى الترويسات القديمة `X-RateLimit-Limit` و `X-RateLimit-Remaining` و `X-RateLimit-Reset`. وتضيف الاستجابة 429 الترويسة `Retry-After`.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchRequest"
              },
              "example": {
                "urls": [
                  "https://example.com/a",
                  "https://example.com/b"
                ],
                "options": {
                  "case_reference": "Smith v. Jones"
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "تم القبول. إعادة إرسال دفعة مكتملة بمفتاح Idempotency-Key نفسه والجسم نفسه تعيد أيضًا 202، مع `Idempotent-Replay: true`؛ وتُعالَج قبل تطبيق حد المعدّل، لذا لا تستهلك أي وحدة منه.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/QuotaLimit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/QuotaRemaining"
              },
              "X-Quota-Used": {
                "$ref": "#/components/headers/QuotaUsed"
              },
              "X-Quota-Cap": {
                "$ref": "#/components/headers/QuotaCap"
              },
              "X-Quota-Org-Limit": {
                "$ref": "#/components/headers/QuotaOrgLimit"
              },
              "X-Quota-Org-Used": {
                "$ref": "#/components/headers/QuotaOrgUsed"
              },
              "X-Sandbox-Limit": {
                "$ref": "#/components/headers/SandboxLimit"
              },
              "X-Sandbox-Remaining": {
                "$ref": "#/components/headers/SandboxRemaining"
              },
              "X-Sandbox-Used": {
                "$ref": "#/components/headers/SandboxUsed"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResult"
                }
              }
            }
          },
          "400": {
            "description": "INVALID_BATCH (في details: الحقلان reason و received، أو index و url وكائن reason متداخل لعنوان URL غير الصالح)، أو MALFORMED_REQUEST، أو خطأ في options. WEBHOOK_REQUIRES_API_KEY: أُرسل webhook_url من جلسة لوحة التحكم (تتطلب webhooks مفتاح API). RATE_LIMIT_COST_TOO_HIGH: عناوين URL أكثر مما تسمح به نافذة زمنية واحدة لحد المعدّل؛ details { reason, max_per_window, window_seconds, requested }.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED أو PAYMENT_REQUIRED. وتُرفض الدفعة أيضًا بالرمز QUOTA_EXCEEDED عندما تتعذّر قراءة الاستخدام الشهري في تلك اللحظة؛ أعد المحاولة.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "EIDAS_REQUIRES_LIVE_KEY: include_eidas مع مفتاح اختبار. EMAIL_NOT_VERIFIED: جلسة لوحة تحكم لم يُؤكَّد عنوان بريدها الإلكتروني. PLAN_ARCHIVE_ONLY: الخطة تحتفظ بالأرشيف فقط ولا تُنشئ أي عمل جديد. PLAN_API_READ_ONLY: مفتاح API لحساب Website Archive (مفتاحه للقراءة فقط؛ استخدم لوحة التحكم). PLAN_API_READ_ONLY مع details.reason \"session_outside_dashboard\": جلسة لوحة تحكم لحساب Website Archive لا يحتوي طلبها على الترويسة Origin أو يحتوي على عنوان ليس من عناوين لوحة تحكم ProofSnap (يُنشأ العمل الجديد في هذه الخطة من لوحة التحكم فقط؛ وهذا قيد مرن وليس حدًّا أمنيًا).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "IDEMPOTENCY_IN_PROGRESS.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "IDEMPOTENCY_KEY_REUSED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: طلبات كثيرة جدًا في النافذة الزمنية الحالية البالغة 60 ثانية. يكرر `error.details.retry_after_seconds` قيمة Retry-After. وتُبلغ RateLimit عن r=0 وعدد الثواني حتى إعادة ضبط النافذة. ويُعاد RATE_LIMITED أيضًا لجلسة لوحة تحكم في Website Archive عند بلوغ حدها اليومي البالغ 100 وحدة في اليوم بتوقيت UTC، وهو حد مشترك بين عمليات الالتقاط المفردة والدفعية (وحدة واحدة لكل عنوان URL) و compare و redact و certify-hash (وحدة واحدة لكل طلب)؛ ويُرفض الطلب الذي لا يتسع له الرصيد المتبقي بالكامل (details.daily_limit و details.remaining و details.action \"wa_session_captures\"؛ ويعطي Retry-After و details.retry_after_seconds عدد الثواني حتى اليوم التالي بتوقيت UTC، ولا تُرسل ترويسات RateLimit و X-RateLimit-* الخاصة بالدقيقة). أما الرفض القصير الذي لا يحمل سوى details.retry_after_seconds و Retry-After: 1 فيعني تحديثًا متزامنًا للعدّاد: أعد المحاولة. والرفض الذي لا يرجع إلى خطأ من المستدعي يعيد الوحدات؛ أما العمل المقبول بالرمز 202 الذي يفشل لاحقًا فتبقى وحدته محتسبة. وهذا قيد مرن وليس حدًّا أمنيًا.\n\nCONCURRENT_CAPTURE_LIMIT: تعذّر بدء أي عنوان URL في الدفعة لأن الحساب يشغّل بالفعل الحد الأقصى المسموح به من عمليات الالتقاط (developer 10، startup 25، growth 100، legal_solo و legal_seats 25، custom 200، website_archive 25، sandbox 5)؛ `details.limit` و `details.in_flight` و `details.requested` و Retry-After. وعندما يتسع المجال لبعضها فقط، تُقبل الدفعة بالرمز 202 وتُدرج البقية في `not_admitted`.\n\nTEST_MODE_DAILY_LIMIT: العمل في وضع الاختبار (مفتاح اختبار، أو جلسة دون فوترة مرتبطة) محدود بـ 200 وحدة لكل حساب في اليوم بتوقيت UTC، وحدة واحدة لكل عنوان URL؛ `details.used` و `details.limit` و `details.reset_at` والترويسات `X-Test-Daily-*` و Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        }
      }
    },
    "/captures/hosts": {
      "get": {
        "tags": [
          "عمليات الالتقاط"
        ],
        "operationId": "listCaptureHosts",
        "summary": "عرض أسماء المضيفين التي التقطتها",
        "description": "كل اسم مضيف في قائمة عمليات الالتقاط الخاصة بك، مع عدد عمليات الالتقاط وآخر وقت التقاط لكل مضيف، لاستخدامه في التصفية حسب النطاق: مرِّر المضيف مرة أخرى بصيغة GET /captures?host=. وقيمة count هي عدد الصفوف التي يعيدها GET /captures?host=<host> دون status (ضمن عمليات الالتقاط المقروءة): عمليات الالتقاط الاختبارية والفعلية على حد سواء، وبكل الحالات بما فيها الموجودة في سلة المهملات (status deleted)، كما تعرضها القائمة؛ وتُستبعد عمليات الالتقاط الممحوة والصفوف التي لا تحتوي على عنوان URL لصفحة (الشهادات). تُحتسب عملية الالتقاط بمجرد أن تحمل اسم المضيف المخزّن (عمليات الالتقاط المنشأة قبل 2026-10-05 تحمله بعد تشغيل الاستكمال الرجعي للبيانات). ولا تُحتسب عملية التقاط الشركة إلا ما دام بإمكانك التصرف فيها (تشغل مقعدًا في تلك الشركة أو تملكها). تُكتب قيمة host بأحرف صغيرة. الترتيب حسب count (الأعلى أولًا)، ثم حسب host (من A إلى Z). 1000 مضيف على الأكثر. تُقرأ أحدث 5000 عملية التقاط؛ وتكون قيمة truncated هي true عندما يتجاوز عدد المضيفين 1000 أو توجد عمليات التقاط أقدم من هذه الـ 5000، لذا قد يغيب مضيف لم يُلتقط إلا قبلها. تُحسب القائمة مرة واحدة على الأكثر لكل حساب كل 30 ثانية، وقد يصل عمرها إلى 30 ثانية (أقدم قليلًا بينما يحسبها طلب آخر للحساب). يستهلك حسابها وحدة واحدة من حد المعدّل في الدقيقة الخاص بك؛ أما الاستجابة المقدَّمة من تلك الذاكرة المؤقتة فلا تستهلك شيئًا. متاح في كل الخطط، لمفاتيح API ولجلسة لوحة التحكم.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "أسماء المضيفين.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "hosts",
                    "truncated"
                  ],
                  "properties": {
                    "hosts": {
                      "type": "array",
                      "maxItems": 1000,
                      "items": {
                        "type": "object",
                        "required": [
                          "host",
                          "count",
                          "last_captured_at"
                        ],
                        "properties": {
                          "host": {
                            "type": "string",
                            "example": "www.example.com",
                            "description": "اسم المضيف بأحرف صغيرة، وهو القيمة التي يقبلها GET /captures?host=."
                          },
                          "count": {
                            "type": "integer",
                            "minimum": 1,
                            "example": 12,
                            "description": "عمليات التقاط هذا المضيف ضمن عمليات الالتقاط المقروءة: الصفوف التي يعيدها GET /captures?host= دون status، بما فيها الموجودة في سلة المهملات."
                          },
                          "last_captured_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true,
                            "example": "2026-10-05T07:48:38Z",
                            "description": "آخر وقت التقاط لهذا المضيف (وقت التقاط الصفحة، وإلا فوقت طلب عملية الالتقاط)، بتوقيت UTC، بدقة الثانية."
                          }
                        }
                      }
                    },
                    "truncated": {
                      "type": "boolean",
                      "description": "true عندما قد تكون القائمة غير كاملة: أكثر من 1000 مضيف، أو عمليات التقاط أكثر من أحدث 5000 عملية مقروءة."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: قيمة scope غير مدعومة (عمليات الالتقاط الخاصة بك فقط).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "description": "RATE_LIMITED: استُنفد حد المعدّل في الدقيقة (يستهلك حساب القائمة وحدة واحدة)، أو أن قائمة حسابك قيد الحساب من طلب آخر ولا توجد نتيجة سابقة مخزّنة؛ أعد المحاولة بعد عدد الثواني المحدد في Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/{capture_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "get": {
        "tags": [
          "عمليات الالتقاط"
        ],
        "operationId": "getCapture",
        "summary": "جلب عملية التقاط",
        "description": "الحالة، وعند الاكتمال رابط package_url جديد صالح لمدة تصل إلى 24 ساعة (انظر package_url_expires_at). يعمل مع معرّفات عمليات الالتقاط والشهادات والمقارنات والنسخ المنقّحة. ويمكن قراءته من الحساب الذي أنشأه، ومن أعضاء القضية التي ينتمي إليها، ومن مالك الشركة التي أُنشئ لصالحها (إذا انتهى اشتراك الشركة، فحتى انتهاء النافذة الزمنية للتنزيل الخاصة بها).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "event",
            "in": "query",
            "required": false,
            "description": "أرسل download عند تنزيل الحزمة: بالنسبة إلى عملية التقاط ضمن قضية، يسجّل ذلك capture.downloaded في سجل الوصول الخاص بالقضية. ويجب ألا تتضمنه الاستعلامات العادية عن الحالة.",
            "schema": {
              "type": "string",
              "enum": [
                "download"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "عملية الالتقاط.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaptureResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: عملية الالتقاط ليست لك.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND: معرّف غير معروف أو غير صالح الصيغة، أو عملية التقاط محذوفة، أو تعذّر العثور على ملف الحزمة.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "عمليات الالتقاط"
        ],
        "operationId": "deleteCapture",
        "summary": "حذف عملية التقاط",
        "description": "افتراضيًا: تُنقل عملية الالتقاط إلى سلة المهملات. ويمكن استعادتها باستخدام PATCH حتى restorable_until (30 يومًا)؛ وبعد ذلك تُزال نهائيًا. ومع permanent=true تُمحى الحزمة المخزّنة فورًا ويُختزل السجل إلى شاهد حذف (tombstone)؛ ولا يمكن التراجع عن ذلك. ولا يزيل أيٌّ منهما الحزم التي نزّلتها بالفعل ولا إثبات OpenTimestamps. وعملية الالتقاط الموجودة في قضية مفتوحة أو مؤرشفة تُزال أولًا من القضية (ويُسجَّل ذلك في سجل الحيازة الخاص بها) عندما يستطيع المستدعي فتح تلك القضية؛ أما المستدعي الذي لا يستطيع ذلك (مُنشئها بعد إزالته من القضية) فيُرفض طلبه بالرمز 409 MATTER_LINKED، سواء للنقل إلى سلة المهملات أو مع permanent=true، إلى أن يزيلها مالك القضية منها؛ ولا يمكن حذف عملية التقاط في قضية مختومة. ولا يمكن حذف عملية التقاط لا تزال قيد المعالجة (409 CAPTURE_PROCESSING)؛ احذفها بعد أن تكتمل أو تفشل. وحذف عملية التقاط لم تكتمل قط يعيد وحدة القبول الشهرية الخاصة بها.\n\nمهلة ما بعد رفع الحفظ القانوني: لمدة 14 يومًا بعد رفع الحفظ القانوني (`deletion_blocked_until` في GET) لا تُحذف عملية الالتقاط نهائيًا (409 CAPTURE_HOLD_GRACE مع `details.until`)، لا من قِبل شخص ولا بتفريغ سلة المهملات ولا وفق سياسة الاحتفاظ (أما حذف الحساب والشركة بعد انتهاء الاشتراك فلا ينتظر هذه المهلة). النقل إلى سلة المهملات مسموح؛ وينتظر تفريغها انتهاء المهلة. ويرفع الحذف النهائي أولًا أي حفظ قانوني من نوع S3 Object Lock لا يزال قائمًا على الكائنات المخزّنة؛ وعندما يرفض التخزين ذلك، يعيد الطلب 409 CAPTURE_LEGAL_HOLD (\"still locked in storage\"، أعد المحاولة لاحقًا) ولا يُحذف شيء. ويُسجَّل كل نقل إلى سلة المهملات وكل استعادة وكل حذف نهائي في سلسلة أحداث عمليات الالتقاط الخاصة بالحساب (GET /captures/events). والنسخة المنقّحة أو المقارنة التي تخضع عملية الالتقاط المصدر الخاصة بها للحفظ القانوني تعيد `source_under_legal_hold: true` عند نقلها إلى سلة المهملات أو حذفها.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "permanent",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "تم الحذف. سلة المهملات: { capture_id, status: \"deleted\", restorable: true, restorable_until }. نهائي: { capture_id, status: \"deleted\", permanent: true }. موجودة بالفعل في سلة المهملات: { capture_id, status }.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "capture_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "restorable": {
                      "type": "boolean"
                    },
                    "restorable_until": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "permanent": {
                      "type": "boolean"
                    },
                    "source_under_legal_hold": {
                      "type": "boolean",
                      "description": "لا يظهر (بالقيمة true) إلا عندما تكون هذه نسخة منقّحة أو مقارنة أُنشئت من عملية التقاط خاضعة للحفظ القانوني."
                    },
                    "deletion_blocked_until": {
                      "type": "string",
                      "format": "date-time",
                      "description": "النقل إلى سلة المهملات خلال مهلة ما بعد الرفع: ينتظر تفريغ سلة المهملات حتى هذا الوقت."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: عملية الالتقاط ليست لك. ويُعاد FORBIDDEN أيضًا عندما تنتمي عملية الالتقاط إلى قضية غير مختومة ولا يستطيع المستدعي (مالك الشركة الذي يتصرف في عملية التقاط تخص صاحب مقعد، وليس مُنشئها) فتح تلك القضية. PLAN_API_READ_ONLY: مفتاح API لحساب Website Archive (مفتاحه للقراءة فقط؛ استخدم لوحة التحكم).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED: عملية الالتقاط مستند مرفق (exhibit) في قضية مختومة. MATTER_LINKED: عملية الالتقاط مستند مرفق في قضية لم يعد المستدعي عضوًا فيها؛ ويزيلها مالك القضية أولًا. CAPTURE_PROCESSING: لا تزال عملية الالتقاط قيد المعالجة (error.details.status). CAPTURE_LEGAL_HOLD: عملية الالتقاط خاضعة للحفظ القانوني ولا يحذفها أحد (لا بالنقل إلى سلة المهملات ولا بالمحو). CAPTURE_HOLD_GRACE: حذف نهائي خلال المهلة التالية لرفع الحفظ القانوني عن عملية الالتقاط (LEGAL_HOLD_RELEASE_GRACE_DAYS، 14 يومًا)؛ ويبيّن error.details.until متى يصبح ممكنًا. والنقل إلى سلة المهملات مسموح خلال المهلة.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: تعذّر محو الحزمة المخزّنة؛ ولم يُحذف شيء.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "عمليات الالتقاط"
        ],
        "operationId": "restoreCapture",
        "summary": "استعادة عملية التقاط محذوفة، أو تعيين ملاحظتها الخاصة، أو فرض الحفظ القانوني عليها أو رفعه",
        "description": "يستعيد عملية التقاط من سلة المهملات. وإذا كانت قد أُزيلت من قضية عند حذفها، فإنها تُضاف إليها مجددًا عندما تكون تلك القضية لا تزال مفتوحة ولا يزال المستدعي قادرًا على فتحها؛ ولا يظهر matter_id و matter_relinked في الاستجابة إلا في هذه الحالة (وتكون قيمة matter_id هي null عندما لم تتم إعادة الربط). والمستدعي الذي لم يعد قادرًا على فتح تلك القضية يستعيد عملية الالتقاط دونها، ولا يُكتب شيء في سجل الحيازة الخاص بالقضية، ولا تذكر الاستجابة القضية. أما عملية الالتقاط غير الموجودة في سلة المهملات فتُعاد دون تغيير.\n\nالملاحظة الخاصة: الجسم الذي يحتوي على `private_note` (سلسلة نصية لا تتجاوز 200 حرف، أو null لمسحها) يعيّن بدلًا من ذلك الملاحظة الخاصة لعملية الالتقاط ولا يفعل شيئًا آخر؛ ولا يمكن دمجه مع `action`. تُخزَّن الملاحظة في سجل عملية الالتقاط فقط، خارج حزمة الأدلة المختومة، لذا لا يغيّر تعديلها الأدلة أو البصمات الخاصة بها أبدًا. ولا يجوز تعيينها إلا لمُنشئ عملية الالتقاط (يحصل مالك الشركة وأعضاء القضية على 403)، على عملية التقاط في أي حالة باستثناء المحذوفة نهائيًا (404). والاستجابة هي { capture_id, status, private_note }.\n\nالحفظ القانوني: الجسم `{ \"action\": \"legal_hold\", \"enabled\": true|false, \"reason\"?: string }` يفرض (true) أو يرفع (false) الحفظ القانوني لعملية الالتقاط ولا يفعل شيئًا آخر. لا تُحذف عملية الالتقاط الخاضعة للحفظ القانوني: يعيد DELETE (إلى سلة المهملات أو نهائيًا) 409 CAPTURE_LEGAL_HOLD للجميع، ولا يمسّها تفريغ سلة المهملات ولا الحذف وفق سياسة الاحتفاظ؛ ويعرض GET القيمة null في `storage.retention.earliest_deletion_at`. ولا يستمر الحفظ إلا ما دام لدى الحساب خطة تحتفظ بالبيانات (أي خطة مدفوعة، بما فيها Archive only؛ ولعملية التقاط الشركة: أن تكون الشركة نشطة أو محفوظة كأرشيف شركة). عند انتهاء الاشتراك، تبقى عمليات الالتقاط متاحة لمدة 30 يومًا؛ وإذا لم تُشترَ خطة من هذا النوع بحلول ذلك الوقت، يُرفع كل حفظ قانوني للحساب (أو للشركة) تلقائيًا في ذلك التاريخ (الفاعل system، والسبب subscription_ended، ويُسجَّل كأي رفع آخر، دون مهلة ما بعد الرفع) وتُحذف عمليات الالتقاط مع غيرها. وللإبقاء على عمليات الالتقاط الخاضعة للحفظ، انتقل إلى Archive only (أو اشترِ أرشيف الشركة) قبل ذلك التاريخ، أو نزّلها. وخلال هذه الأيام الثلاثين لا يمكن فرض حفظ جديد (ليس لدى الحساب خطة مدفوعة)، ولكن يظل بالإمكان رفع الحفظ القائم. يُخزَّن الحفظ في سجل عملية الالتقاط فقط، خارج حزمة الأدلة المختومة. و `reason` نص عادي اختياري لا يتجاوز 200 حرف (دون أحرف تحكم أو أحرف غير مرئية أو أحرف اتجاه النص؛ وتُدمج المسافات البيضاء المتتالية، والقيمة الفارغة تعني عدم وجود سبب)؛ ويُحفظ مع الحفظ القانوني ويُمسح عند رفعه.\n\nمن يحق له ذلك: فرض الحفظ متاح لكل من يجوز له التصرف في عملية الالتقاط (مُنشئها، وفي حالة عملية التقاط الشركة ما دام يشغل مقعدًا في تلك الشركة، ومالك الشركة، بما في ذلك شركة انتهى اشتراكها ما دامت ضمن مدة الاحتفاظ أو محفوظة كأرشيف شركة؛ وللأرشيف عبر جلسة لوحة التحكم فقط). أما رفعه: ففي عملية الالتقاط الشخصية لمُنشئها، وفي عملية التقاط الشركة لمالك الشركة فقط (يحصل صاحب المقعد على 403 LEGAL_HOLD_OWNER_ONLY). ويتطلب فرض الحفظ خطة مدفوعة (كل خطة مدفوعة، بما فيها Archive only): يحصل وضع الاختبار (sandbox) المجاني، وكذلك الحساب الذي انتهى اشتراكه، على 403 LEGAL_HOLD_NOT_AVAILABLE مع details.reason \"plan\" (ويُعدّ أرشيف الشركة الساري مدفوعًا لعمليات التقاط تلك الشركة، عبر جلسة لوحة التحكم الخاصة بمالكها)، وتحصل عملية الالتقاط في وضع الاختبار (التي تحمل علامة مائية، والمُجراة بمفتاح اختبار أو دون فوترة مرتبطة) على 403 LEGAL_HOLD_NOT_AVAILABLE مع details.reason \"test_capture\". ورفع الحفظ متاح في كل خطة، حتى بعد انتهاء الاشتراك. وفي Website Archive يكون مفتاح API للقراءة فقط (403 PLAN_API_READ_ONLY)، بينما يجوز ذلك لجلسة لوحة التحكم. ولا يمنع ذلك اشتراكٌ متأخر السداد أو غير مدفوع. وتحصل جلسة لوحة التحكم أو المفتاح الذي لم يُؤكَّد عنوان بريده الإلكتروني على 403 EMAIL_NOT_VERIFIED.\n\nيُرفض الطلب على عملية التقاط موجودة في سلة المهملات (409 CAPTURE_IN_TRASH: استعدها أولًا) أو لا تزال قيد المعالجة (409 CAPTURE_PROCESSING)؛ أما عملية الالتقاط المحذوفة نهائيًا، أو التي يجري حذفها النهائي، فتعيد 404. ويستهلك كل طلب حفظ قانوني وحدة واحدة من حد المعدّل في الدقيقة الخاص بالحساب (429 RATE_LIMITED مع Retry-After عند استنفاده). وإرسال الحالة التي تكون عليها عملية الالتقاط بالفعل لا يغيّر شيئًا ويعيد 200 مع الحقول المخزّنة و `changed: false`. ويُسجَّل كل تغيير في سجل التدقيق باسم capture.legal_hold (ولعملية التقاط الشركة في المستأجر (tenant) الخاص بالشركة)، ويُدوَّن في سجل الوصول إلى عملية الالتقاط (`legal_hold_on` / `legal_hold_off`، ويُحتفظ به عند محو عملية الالتقاط لاحقًا)، وعندما تنتمي عملية الالتقاط إلى قضية، يُكتب في سجل الحيازة الخاص بالقضية باسم capture.legal_hold_placed أو capture.legal_hold_released مع معرّف عملية الالتقاط هدفًا له (ويذهب السبب إلى سجل الوصول الخاص بالقضية، ولا يدخل أبدًا في سلسلة البصمات؛ ولا تتغير سلسلة القضية المختومة). والاستجابة هي { capture_id, legal_hold, legal_hold_set_at, legal_hold_reason, changed }.\n\nالحفظ القانوني v2: يؤدي فرض الحفظ أو رفعه أيضًا إلى فرض أو رفع حفظ قانوني من نوع S3 Object Lock على كل إصدار من الحزمة المخزّنة، وعلى إثبات OpenTimestamps المُحدَّث الخاص بها، وعلى النسخة المكررة المخزّنة بشكل منفصل (`s3_legal_hold` في GET؛ وعندما يرفض التخزين ذلك أو لا يحتوي على إعداد Object Lock، يظل الحفظ في سجل عملية الالتقاط ساريًا، ويعيد الخادم المحاولة الفاشلة). ويبدأ الرفع مهلة مدتها 14 يومًا لا تُحذف خلالها عملية الالتقاط نهائيًا (ولا ينطبق ذلك بعد انتهاء الاشتراك: إذ تحل محلها الأيام الثلاثون التالية للانتهاء). والحفظ المفروض عبر الحفظ القانوني لقضيته (`legal_hold_source` \"matter:<id>\") يُرفع على مستوى القضية ما دام ذلك الحفظ قائمًا: ورفعه هنا يعيد 409 LEGAL_HOLD_MATTER (error.details.matter_id لمن يحق له قراءة القضية). ويُسجَّل كل تغيير في سلسلة أحداث عمليات الالتقاط الخاصة بالحساب (GET /captures/events).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "description": "restore (القيمة الافتراضية للجسم الذي لا يحتوي على private_note) أو legal_hold (مع enabled و reason الاختياري).",
                    "enum": [
                      "restore",
                      "legal_hold"
                    ]
                  },
                  "private_note": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/PrivateNote"
                      }
                    ],
                    "description": "تعيين (سلسلة نصية) أو مسح (null أو سلسلة فارغة) الملاحظة الخاصة. لا يمكن دمجه مع action."
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "مع action legal_hold (وهو مطلوب فيها): true يفرض الحفظ القانوني، و false يرفعه."
                  },
                  "reason": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 200,
                    "description": "مع action legal_hold، اختياري: سبب إخضاع عملية الالتقاط للحفظ، نص عادي لا يتجاوز 200 حرف. يُخزَّن مع الحفظ، خارج حزمة الأدلة؛ ويُتجاهَل عند الرفع (وعندئذ يذهب إلى سجل التدقيق فقط)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "تمت الاستعادة ({ capture_id, status, restored: true, matter_id?, matter_relinked? })، أو دون تغيير ({ capture_id, status })، أو عُيِّنت الملاحظة الخاصة ({ capture_id, status, private_note })، أو عُيِّن الحفظ القانوني ({ capture_id, legal_hold, legal_hold_set_at, legal_hold_reason, changed }).",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "capture_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "restored": {
                      "type": "boolean"
                    },
                    "matter_id": {
                      "type": "string",
                      "nullable": true
                    },
                    "matter_relinked": {
                      "type": "boolean"
                    },
                    "private_note": {
                      "type": "string",
                      "nullable": true
                    },
                    "legal_hold": {
                      "type": "boolean",
                      "description": "إجراء الحفظ القانوني: حالة الحفظ لعملية الالتقاط بعد الطلب."
                    },
                    "legal_hold_set_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "إجراء الحفظ القانوني: وقت فرض الحفظ الحالي (null عند عدم وجوده)."
                    },
                    "legal_hold_reason": {
                      "type": "string",
                      "nullable": true,
                      "description": "إجراء الحفظ القانوني: السبب المخزّن مع الحفظ الحالي، أو null."
                    },
                    "changed": {
                      "type": "boolean",
                      "description": "إجراء الحفظ القانوني: false عندما تكون عملية الالتقاط على الحالة المطلوبة بالفعل (لم يتغير شيء ولم يُسجَّل شيء)."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: JSON غير صالح، أو جسم ليس كائنًا، أو action غير restore أو legal_hold، أو private_note مدمج مع action، أو private_note ليس سلسلة نصية أو null، أو يتجاوز 200 حرف، أو يحتوي على أحرف تحكم أو أحرف غير مرئية أو أحرف اتجاه النص. وبالنسبة إلى legal_hold: غياب enabled أو كونه ليس قيمة منطقية، أو reason ليس سلسلة نصية أو يتجاوز 200 حرف أو ليس نصًا عاديًا، أو حقل غير معروف.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: عملية الالتقاط ليست لك. ويُعاد FORBIDDEN أيضًا عندما تنتمي عملية الالتقاط إلى قضية غير مختومة ولا يستطيع المستدعي (مالك الشركة الذي يتصرف في عملية التقاط تخص صاحب مقعد، وليس مُنشئها) فتح تلك القضية. PLAN_API_READ_ONLY: مفتاح API لحساب Website Archive (مفتاحه للقراءة فقط؛ استخدم لوحة التحكم). وبالنسبة إلى private_note: يُعاد FORBIDDEN لأي شخص غير مُنشئ عملية الالتقاط (الذي يجب أن يظل مسموحًا له بالتصرف فيها). وبالنسبة إلى legal_hold: يُعاد FORBIDDEN عندما لا يجوز للمستدعي التصرف في عملية الالتقاط؛ و LEGAL_HOLD_OWNER_ONLY عندما يرفع صاحب مقعد (وليس مالك الشركة) الحفظ عن عملية التقاط الشركة؛ و LEGAL_HOLD_NOT_AVAILABLE عند فرض الحفظ دون خطة مدفوعة (details.reason plan) أو على عملية التقاط في وضع الاختبار (details.reason test_capture)؛ و EMAIL_NOT_VERIFIED لعنوان بريد إلكتروني غير مؤكَّد. ولا تنطبق قاعدة القضية المذكورة أعلاه على الحفظ القانوني.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND، بما في ذلك عملية التقاط حُذفت نهائيًا.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "للحفظ القانوني فقط. CAPTURE_IN_TRASH: عملية الالتقاط في سلة المهملات؛ استعدها أولًا. CAPTURE_PROCESSING: لا تزال عملية الالتقاط قيد المعالجة (error.details.status). LEGAL_HOLD_MATTER: فُرض الحفظ على عملية الالتقاط عبر الحفظ القانوني لقضيتها، وهو لا يزال قائمًا؛ ارفعه على مستوى القضية. LEGAL_HOLD_SOURCE: حفظ نسخة منقّحة أو مقارنة يتبع عملية الالتقاط التي أُنشئت منها (وفي المقارنة، أيًّا من عمليتي الالتقاط المقارَنتين)، ولا تزال تلك العملية خاضعة للحفظ القانوني (المفروض عليها مباشرة أو عبر قضيتها): تظل الحزمة خاضعة للحفظ إلى أن يُرفع ذلك الحفظ (`details.source_capture_id`). وتظل المقارنة خاضعة للحفظ ما دامت أيٌّ من عمليتي الالتقاط المقارَنتين خاضعة له، ولا يُرفع عنها إلا عندما لا تكون أيٌّ منهما كذلك (يحدد `legal_hold_source` \"derived:<capture id>\" العملية الخاضعة للحفظ).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "للحفظ القانوني فقط. RATE_LIMITED: استُنفد حد المعدّل في الدقيقة للحساب. يكرر `error.details.retry_after_seconds` قيمة Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/{capture_id}/verify": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "post": {
        "tags": [
          "عمليات الالتقاط"
        ],
        "operationId": "verifyCapture",
        "summary": "التحقق من حزمة مخزّنة",
        "description": "تقرأ الحزمة المخزّنة وتتحقق من توقيع RSA على manifest.json ومن بصمة SHA-256 لكل ملف يدرجه. تعيد الحزم التي يتجاوز حجمها 256 MiB الخطأ 413 VERIFY_PACKAGE_TOO_LARGE؛ تحقّق منها دون اتصال بالبرامج النصية المرفقة. يكلّف كل طلب وحدة واحدة من حد المعدّل في الدقيقة للحساب (429 RATE_LIMITED). عندما تحمل الحزمة manifest.json.tsr، يتحقق ProofSnap أيضًا من رمز الطابع الزمني وفق eIDAS (checks.eidas): توقيع الرمز وسماته الموقَّعة، وأنه يحمل بصمة SHA-256 للملف manifest.json، وextendedKeyUsage لشهادة سلطة الطوابع الزمنية (TSA)، وسلسلة شهاداتها حتى شهادة الجذر المثبّتة Disig QCA4 كما كانت في وقت الرمز (genTime)، وحالة شهادة TSA من استجابة OCSP أو قائمة CRL المسجّلة عند الإصدار في eidas_validation.json (يُتحقق منها بالتوقيع، ولا تُجلب مباشرة). لا يطلب هذا الفحص طابعًا زمنيًا جديدًا أبدًا. ويُبلغ أيضًا عمّا إذا كان manifest.json.ots موجودًا وعمّا إذا كانت بصمة البيان مدرجة في السجل العام للحزم الصادرة من ProofSnap. يعتمد الحكم على توقيع البيان والملفات المدرجة فقط؛ والرمز الذي يفشل فحصه يُبلَّغ عنه في checks.eidas وفي warnings (EIDAS_TOKEN_FAILED)، ولا يُدمج في الحكم. الحزمة الصادرة بطابع زمني مؤهل تسجّل eidas_requested: true في manifest.json الموقَّع الخاص بها؛ وعندما لا تعود مثل هذه الحزمة تحمل manifest.json.tsr، يُبلَّغ عن checks.eidas وchecks.eidas_disig على أنهما مفقودان مع التحذير EIDAS_TOKEN_MISSING (وتُبلغ البرامج النصية للتحقق المرفقة بالنتيجة FAIL للحزمة نفسها). مسموح لمالك عملية الالتقاط وأعضاء قضيتها ومالك الشركة التي أُجريت لها. للتحقق من الطوابع الزمنية بشكل مستقل، شغّل البرامج النصية الموجودة في المجلد verification/ داخل الحزمة.\n\nالحزم المستوردة (source import): يُعاد فحص ملف ZIP المخزّن بفحوص الاستيراد (التوقيع، وكل ملف، وسلسلة الحيازة، وسلسلة البصمات في سجل التحليل الجنائي الرقمي، والطوابع الزمنية؛ ويُبحث عن المنشأ (provenance) من جديد في السجل العام لحزم API بالنسبة إلى حزم API، ويؤخذ من سجل الاستيراد بالنسبة إلى حزم الإضافة). الاستجابة هي { capture_id, source, type: \"import\", package_kind, verifier: \"package-verify\", verdict (match, modified, invalid), rechecked: true, manifest_sha256, zip_sha256, zip_matches_import, kind, summary, levels, checks, reasons, provenance }. الحزمة التي تتجاوز 512 MiB، أو الفحص الذي لا ينتهي في نحو 20 ثانية، تعيد rechecked: false مع سبب (too_large_for_synchronous_check، time_budget، storage_error)، وverdict: null وstored_verification (التقرير المسجّل وقت الاستيراد).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "نتيجة التحقق.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyResult"
                },
                "example": {
                  "verdict": "match",
                  "capture_id": "cap_a1B2c3D4e5F6g7H8",
                  "source": "s3",
                  "checks": {
                    "manifest_signature": {
                      "status": "valid",
                      "algorithm": "RSA-4096"
                    },
                    "declared_files": {
                      "status": "all_match"
                    },
                    "bitcoin_ots": {
                      "status": "present"
                    },
                    "eidas_disig": null,
                    "proofsnap_issuance": {
                      "status": "issued_by_proofsnap",
                      "manifest_sha256": "3f2a...",
                      "issued_at": "2026-09-01T10:00:00.000Z",
                      "type": "capture"
                    },
                    "eidas": null
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND: عملية التقاط غير معروفة أو محذوفة، أو لا توجد لها حزمة مخزّنة.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "VERIFY_PACKAGE_TOO_LARGE: يتجاوز حجم الحزمة المخزّنة 256 MiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: استُنفد حد المعدّل في الدقيقة للحساب.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "CONFIGURATION_ERROR: تعذّرت قراءة الحزمة المخزّنة.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/{capture_id}/redact": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "post": {
        "tags": [
          "المقارنة والتنقيح"
        ],
        "operationId": "redactCapture",
        "summary": "إنشاء نسخة منقّحة",
        "description": "تُنشئ من عملية التقاط مكتملة حزمة جديدة يُزال منها المحتوى المحدد، وتوقّعها من جديد (بمفتاح RSA-4096 جديد) وترسلها لتثبيتها عبر OpenTimestamps. لا يتغير الأصل. متزامن: يعيد 201 عندما تصبح الحزمة جاهزة. تُحتسب كعملية التقاط واحدة.\n\n- تنقيح المربعات يسوّد مناطق من لقطة الشاشة. تُنقَّح لقطة الشاشة بصيغة JPEG بالشكل الذي تكتبه عملية الالتقاط (sequential، بتدرج رمادي أو YCbCr) دون فك ترميزها إلى بكسلات: تُستبدل الكتل المضغوطة (MCU، من 8 إلى 32 بكسلًا في الضلع) التي يلمسها المربع بكتل سوداء وتبقى كل الكتل الأخرى دون تغيير، فلا يُعاد ضغط بقية الصورة (`screenshot_method` `jpeg_dct_blocks`، حتى 150 ميغابكسل). المنطقة المسوّدة هي كل مربع موسَّعًا إلى كتل كاملة (`painted_rects`)، لذا تحتوي المربع دائمًا. أما لقطة الشاشة بصيغة PNG أو JPEG من نوع آخر فيُفك ترميزها وتُطلى ويُعاد ترميزها في screenshot.jpeg، ثم تُسوَّد في الملف الجديد الكتل 8 x 8 التي يلمسها المربع (`screenshot_method` `decode_repaint_jpeg`، حتى 60 ميغابكسل). تُرفض لقطة الشاشة التي تتجاوز حدها أو التي تكون بيانات صورتها غير مقروءة بالخطأ 422 REDACTION_SCREENSHOT_UNREADABLE قبل احتساب أي شيء. إذا كانت الترويسة مقروءة وبيانات الصورة غير مقروءة، يُكتشف الخطأ بعد حجز الوحدة الشهرية، وتُحرَّر تلك الوحدة. يعمل تنقيح النص والبيانات الوصفية دون مربعات أيضًا. يسجّل redaction_manifest.json (`screenshot_boxes`) الطريقة و`painted_rects` وشبكة الكتل وملاحظة توضيحية لخبراء الأدلة الجنائية الرقمية.\n- تنقيح النص يزيل المطابقات من page.html وdomtextcontent.txt، ومن كل قيمة نصية حرة في metadata.json (عنوان الصفحة وقيم meta، وسجلات WHOIS / RDAP، وحقل subject في TLS، وقيم ترويسات استجابة HTTP)، ومن كل قيمة نصية حرة في forensic_log.json وchain_of_custody.json (case_reference وcase_id وcase_note وقيم metadata الخاصة بك، والشيفرة المحقونة وأخطاؤها، وتسميات النوافذ المنبثقة المغلقة، وlegal_context.note). تبقى المعرّفات والبصمات والأوقات وعناوين URL في تلك السجلات دون تغيير. تُزال الصيغ المرمّزة أيضًا: مراجع أحرف HTML (المسماة والعشرية والست عشرية، مثل M&uuml;ller أو jan&#64;firma.cz)، وترميز النسبة المئوية (mailto:jan%40firma.cz)، وتهريب JavaScript (\\u0040، \\x40)، بما في ذلك الترميزات المتداخلة حتى مستويين؛ وتحل العلامة محل الأحرف المرمّزة نفسها. يقرأ فحص المطابقات المتبقية بعد التنقيح ملفات JSON قيمةً قيمةً ويبحث في كل نص بصيغته المكتوبة وبعد فك ترميزه، فيفشل عند وجود مطابقة بأي من هذه الصيغ. يسوّد تنقيح النص أيضًا لقطة الشاشة حيث يحدد text_layout.json الخاص بعملية الالتقاط المصدر موضع ظهور (`text_rule_rects`): تُطلى الأسطر المقدّرة من كتلة النص التي تحتوي المطابقة، ولا تُطلى الكتلة كلها إلا عندما يكون التقدير غير مؤكد (يُحتسب في `screenshot_boxes.from_text_rules.rects_whole_block` من redaction_manifest.json)؛ أما المواضع التي لا يحددها فتُحتسب في `text_occurrences_not_located`. تطابق القواعد حالة الأحرف بدقة ما لم يُضبط `case_insensitive`.\n- تنقيح البيانات الوصفية يزيل حقولًا من metadata.json. يمتد أثر اسمين أبعد من ذلك. يزيل `ip_dns` (ويُقبل أيضًا: `ip`، `dns`، `resolved_ips`) عناوين IP الخاصة بالموقع المستهدف وإجابات DNS من metadata.json وforensic_log.json وchain_of_custody.json (تبقى عناوين جهاز الالتقاط نفسه)، ويزيلها أيضًا، عناوينَ كاملة فقط، من page.html وdomtextcontent.txt ولقطة الشاشة كقواعد نصية تلقائية (تُسجَّل بالمصدر ip_dns فقط، ولا يُسجَّل العنوان أو بصمته أبدًا، ولا تُحتسب ضمن حد text_redactions). يزيل `cookie_names` (وأيضًا `cookie`، `cookies`، `http_headers.cookie`) الترويستين Cookie وSet-Cookie وكل قائمة بأسماء ملفات تعريف الارتباط في تلك السجلات؛ وتُحفظ الأعداد. قيم ملفات تعريف الارتباط منقّحة أصلًا في كل عملية التقاط.\n\nالمربع يغطي البكسلات فقط. يبقى النص الواقع تحت المربع في page.html وdomtextcontent.txt وmetadata.json ما لم تضف أيضًا تنقيحًا للنص، وقد تبقى الصورة الواقعة تحت المربع مُشارًا إليها أو مضمَّنة في page.html (img src أو srcset، أو CSS url()، أو data: URI)؛ ويزيل تنقيحُ النص المطابق لعنوانها تلك الإشارة. لا تحمل الحزمة المنقّحة من الأصل إلا screenshot.jpeg (أو screenshot.png إذا لم يستبدلها أي مربع) وpage.html وdomtextcontent.txt وmetadata.json وforensic_log.json، إضافة إلى الملفات التي تكتبها من جديد (evidence.pdf وchain_of_custody.json وredaction_manifest.json وملفات التحقق وملفات البيان). يُستبعد كل ملف آخر من الأصل، مثل images/ وimage_inventory.json وtext_layout.json وpage.mhtml وnetwork.har وcapture.warc، ويُدرج في redaction_manifest.json تحت dropped_for_leak_protection. يُطبَّق تنقيح النص على أسماء هذه الملفات أيضًا، لأن نسخة الصورة تُسمّى بحسب عنوان URL الخاص بها. يحتفظ chain_of_custody.json بأحداث الأصل المتعلقة بها، موسومةً بـ artifact_removed_in_redaction ودون بصمات الملفات التي لا تحتويها الحزمة المنقّحة. ويُزال أيضًا rule_902_certification.pdf الخاص بالأصل، لأنه يسرد ملفات الأصل وبصماتها؛ وتُدرج الإزالة في redaction_manifest.json (removed_as_stale) وفي chain_of_custody.json. لا يُنقَّح عنوان URL الخاص بعملية الالتقاط أبدًا لأنه جزء من البيان الموقَّع: تنقيح النص الذي يوجد نصه أيضًا في عنوان URL الخاص بعملية الالتقاط أو في عنوان URL النهائي يُطبَّق في كل مكان آخر، ويبقى النص في ذلك العنوان، وتُدرج القاعدة في `rules_in_capture_url` وفي redaction_manifest.json (leakage_check.capture_url). لا يمكن تنقيح إلا عمليات التقاط صفحات الويب (تُرفض المقارنة أو التوثيق أو التنقيح بالخطأ 422 REDACTION_SOURCE_UNSUPPORTED).\n\nتنقيح عملية التقاط اختبارية (أُجريت بمفتاح اختبار، أو في وضع الاختبار (sandbox)، أو بحساب غير مرتبط بحساب فوترة) هو حزمة اختبارية أيًّا كان المفتاح الذي يطلبه: بعلامة مائية، و`test_mode: true` في manifest.json، وصف اختباري (key_env test) لا يُحتسب ولا يستهلك وحدة شهرية. يُرفض include_eidas في مثل هذا التنقيح بالخطأ 403 EIDAS_REQUIRES_LIVE_KEY (details.reason).\n\nيُعرض تقرير التنقيح (evidence.pdf) باللغة `language` (الافتراضية: لغة عملية الالتقاط المصدر)، بما في ذلك متنه وصفحة غلافه وشريط الاختبار.\n\nقبل تنقيح أي شيء، يُعاد حساب بصمة كل ملف يقرؤه التنقيح أو يحمله (لقطة الشاشة وpage.html وdomtextcontent.txt وmetadata.json وforensic_log.json وchain_of_custody.json وtext_layout.json) ويدرجه البيان المصدر مع بصمة SHA-256 (redaction_manifest.json derived_from.source_files_rehashed)؛ وعند عدم التطابق أو غياب ملف يُعاد 422 REDACTION_SOURCE_INTEGRITY_FAILED ولا يصدر شيء، فلا يُختم مصدر تالف أو معدَّل من جديد تحت توقيع جديد أبدًا. لا تُقرأ الملفات التي تستبعدها الحزمة المنقّحة. التنقيح المتزامن الذي استغرقت قراءة مصدره وقتًا طويلًا يتوقف قبل حجز أي شيء، بالخطأ 500 CAPTURE_TIME_BUDGET_EXCEEDED.\n\nيخضع تنقيح عملية التقاط خاضعة للحفظ القانوني للحفظ القانوني أيضًا (legal_hold_source \"derived:<capture id>\")، وكذلك كل تنقيح لعملية التقاط عند وضع حفظ عليها؛ ورفع الحفظ عن عملية الالتقاط يرفعه عن تلك التنقيحات، وما دامت عملية الالتقاط محفوظة لا يمكن رفع الحفظ عن هذه التنقيحات منفردةً (409 LEGAL_HOLD_SOURCE). قيمة captured_at في صف التنقيح هي وقت التقاط مصدره؛ وredacted_at هو وقت إجراء التنقيح.\n\nيُرفض التنقيح احتياطيًا (fail closed): عندما يجد فحص ما بعد التنقيح مصطلحًا منقّحًا لا يزال في أي مكان من الحزمة خارج عنوان URL الخاص بعملية الالتقاط وعنوان URL النهائي، لا تصدر أي حزمة ويعيد الطلب 422 REDACTION_LEAK_DETECTED (`error.details` `{ files, rule_indexes, metadata_fields, in_capture_url }`؛ ولا يُعاد المصطلح نفسه أبدًا). يسجّل redaction_manifest.json القيمة `leakage_check.fail_closed: true`.\n\nتُقتطع القيم عند حدود الأحرف (لا يُقسم حرف أبدًا): سلاسل مطابقة النص إلى 2000 حرف، وreasons إلى 200، وreason_summary إلى 500، وحقول custodian إلى 200. تُطبع reasons وreason_summary وحقول custodian على شهادة التنقيح ويجب أن تكون نصًا عاديًا: يُرفض حرف التحكم أو الحرف غير المرئي أو حرف اتجاه النص بالخطأ 400.\n\nالميزانية الزمنية وإعادة المحاولة: يعيد الطلب استجابته ضمن حد API Gateway البالغ 29 ثانية. يجري الختم ضمن ميزانية زمنية تُحسب من بداية الطلب؛ وإذا لم يتسع الطابع الزمني المؤهل (include_eidas) ضمن الميزانية، فلا يُطلب وتُسلَّم الحزمة مع تثبيت OpenTimestamps فقط (timestamps.eidas بالحالة not_obtained والسبب time_budget، دون فوترة)، ويُستبعد طلب OpenTimestamps الذي لا يتسع (يسجّل chain_of_custody.json الحدث ots_skipped بالسبب time_budget). أرسل Idempotency-Key كي تعيد إعادة المحاولة (مثلًا بعد 504 من بوابة أو خطأ في الشبكة) التنقيحَ نفسه بدلًا من إنشاء تنقيح ثانٍ وفوترته. إعادة المحاولة أثناء استمرار الطلب الأول تعيد 409 IDEMPOTENCY_IN_PROGRESS مع معرّف التنقيح في `error.details.capture_id` (استعلم به عبر GET /captures/{capture_id}). وإعادة المحاولة اللاحقة تعيد 201 مع جسم الاستجابة الأصلي و`Idempotent-Replay: true`. التنقيح الذي يفشل يحرّر مفتاحه، فتُنفَّذ إعادة المحاولة بالمفتاح نفسه من جديد.\n\nيُطبَّق حد المعدّل على كل حساب في نوافذ زمنية مدتها 60 ثانية (تتشارك كل مفاتيح API في الحساب وجلسة لوحة التحكم الخاصة به نافذة واحدة). بعد تشغيل المحدِّد، تتضمن استجابات هذا المسار الترويستين `RateLimit-Policy` و`RateLimit` (استنادًا إلى IETF draft-ietf-httpapi-ratelimit-headers) إضافة إلى الترويسات القديمة `X-RateLimit-Limit` و`X-RateLimit-Remaining` و`X-RateLimit-Reset`. وتضيف الاستجابة 429 الترويسة `Retry-After`.\n\nلا يمكن تنقيح حزمة أدلة مستوردة (source import، معرّفات تبدأ بـ `imp_`): 422 IMPORT_NOT_SUPPORTED.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "boxes": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "x": {
                          "type": "number",
                          "minimum": 0
                        },
                        "y": {
                          "type": "number",
                          "minimum": 0
                        },
                        "w": {
                          "type": "number",
                          "minimum": 0
                        },
                        "h": {
                          "type": "number",
                          "minimum": 0
                        },
                        "reason": {
                          "type": "string",
                          "maxLength": 200
                        }
                      },
                      "required": [
                        "x",
                        "y",
                        "w",
                        "h"
                      ]
                    },
                    "maxItems": 200,
                    "description": "مستطيلات بالبكسل في لقطة الشاشة الخاصة بعملية الالتقاط المصدر (x وy من زاويتها العلوية اليسرى، وw وh العرض والارتفاع). عندما تكون القيم الأربع لمربع ما كلها بين 0 و1 تُقرأ كنِسب من عرض لقطة الشاشة وارتفاعها. المربع الواقع بالكامل خارج لقطة الشاشة لا يطلي شيئًا ويُدرج في ignored_boxes؛ والمربع الذي يتجاوز حافةً يُقص عند الحافة ويُدرج في clipped_boxes. عندما تقع كل المربعات خارجها ولا يغيّر أي تنقيح للنص أو البيانات الوصفية شيئًا، يُرفض الطلب بالخطأ 400 REDACTION_NO_OPS قبل احتساب أي شيء."
                  },
                  "text_redactions": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "match": {
                          "oneOf": [
                            {
                              "type": "string",
                              "description": "نص حرفي.",
                              "minLength": 1
                            },
                            {
                              "type": "object",
                              "properties": {
                                "pattern": {
                                  "type": "string",
                                  "description": "تعبير نمطي (regular expression) من 1 إلى 500 حرف. تُرفض الأنماط ذات المكمّمات المتداخلة مثل (a+)+ بالخطأ 400.",
                                  "minLength": 1,
                                  "maxLength": 500
                                },
                                "flags": {
                                  "type": "string",
                                  "description": "أيٌّ من g وi وm وs وu، كلٌّ منها مرة واحدة على الأكثر. يُرفض y (sticky) بالخطأ 400: إذ سيطابق في موضع واحد فقط ويترك المواضع الأخرى.",
                                  "pattern": "^[gimsu]*$"
                                }
                              },
                              "required": [
                                "pattern"
                              ]
                            }
                          ]
                        },
                        "case_insensitive": {
                          "type": "boolean",
                          "default": false,
                          "description": "مطابقة سلسلة حرفية دون مراعاة حالة الأحرف (للتعبير النمطي استخدم العلم i)."
                        },
                        "reason": {
                          "type": "string",
                          "maxLength": 200,
                          "description": "نص عادي، يُطبع على شهادة التنقيح."
                        }
                      },
                      "required": [
                        "match"
                      ]
                    },
                    "maxItems": 200
                  },
                  "metadata_fields": {
                    "type": "array",
                    "description": "مسارات حقول في metadata.json (يطابق المقطع `*` كل عنصر في المصفوفة)، أو الأسماء `ip_dns` و`cookie_names` و`authorization` (انظر أعلاه).",
                    "items": {
                      "oneOf": [
                        {
                          "type": "string",
                          "description": "مسار حقل في metadata.json."
                        },
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string"
                            },
                            "reason": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "field"
                          ]
                        }
                      ]
                    },
                    "maxItems": 100
                  },
                  "reason_summary": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "نص عادي، يُطبع على شهادة التنقيح."
                  },
                  "custodian": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "title": {
                        "type": "string"
                      }
                    },
                    "description": "يُطبع على تقرير التنقيح. يخضع name وtitle لقاعدة النص العادي (انظر PlainText)."
                  },
                  "language": {
                    "type": "string",
                    "description": "لغة ملفات PDF المخصصة للقراءة البشرية. إحدى القيم: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. تُقبل الاختلافات في حالة الأحرف والشرطة والمنطقة (تُقبل \"DE\" و\"fr-FR\" و\"PT-BR\")؛ وتُرفض \"pt\" و\"zh\" المجردتان. تعيد القيمة غير المدعومة 400 MALFORMED_REQUEST. اختياري: من دونه (أو إذا كان فارغًا) يكون التقرير بلغة عملية الالتقاط المصدر."
                  },
                  "include_eidas": {
                    "type": "boolean",
                    "description": "كما في CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "عنوان URL بصيغة https يتلقى redaction.completed. يُرسل الحدث مباشرة بعد الاستجابة 201، وليس قبلها. يجب أن يستخدم العنوان https وألا يشير إلى عنوان خاص أو loopback أو link-local أو carrier-grade NAT أو عنوان بيانات وصفية سحابية (القواعد نفسها المطبقة على عنوان URL لعملية الالتقاط؛ وإلا 422 SSRF_BLOCKED). القيمة الفارغة أو المكوّنة من مسافات فقط تعني عدم وجود webhook. يتطلب مفتاح API: إذا أُرسل من جلسة لوحة التحكم يُرفض بالخطأ 400 WEBHOOK_REQUIRES_API_KEY، لأن رسائل webhook تُوقَّع بسر webhook الخاص بالمفتاح.",
                    "format": "uri"
                  },
                  "rule_commitment_salt": {
                    "type": "string",
                    "description": "اختياري. ملح خاص بك بطول 32 بايت لالتزامات القواعد (redaction_manifest.json rule_commitments)، على شكل 64 رقمًا ست عشريًا أو base64 / base64url؛ ويُرفض الملح المكوّن من بايت واحد مكرر (400). أرسله لتمتلك الملح قبل الاستدعاء: من دونه ينشئ الخادم ملحًا ويعيده في الاستجابة 201 فقط، فإذا فُقدت الاستجابة بسبب انتهاء المهلة (504 من البوابة بينما اكتمل التنقيح فعلًا) يضيع الملح، ولا تحمله الاستجابة المُعادة عبر Idempotency-Key. لا يحتفظ به ProofSnap."
                  }
                },
                "description": "واحد على الأقل من boxes أو text_redactions أو metadata_fields."
              },
              "example": {
                "text_redactions": [
                  {
                    "match": "jane.doe@example.com",
                    "reason": "personal data"
                  }
                ],
                "reason_summary": "Personal data of a third party"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "الحزمة المنقّحة جاهزة. تعيد إعادة الإرسال الآمنة (idempotent replay) لتنقيح سابق بمفتاح Idempotency-Key نفسه أيضًا 201 مع `Idempotent-Replay: true`: جسم الاستجابة الأصلي، مع الحالة الحالية وpackage_url موقَّع مسبقًا من جديد (يُحذف عندما يتعذر توقيع رابط الحزمة مسبقًا؛ اقرأه عبر GET /captures/{capture_id}).",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "redaction_id": {
                      "type": "string",
                      "example": "red_a1B2c3D4e5F6g7H8"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "completed"
                      ]
                    },
                    "redacted_from": {
                      "type": "string"
                    },
                    "redaction_count": {
                      "type": "integer",
                      "description": "عدد إجراءات التنقيح المطبَّقة."
                    },
                    "original_signature_verified": {
                      "type": "boolean",
                      "description": "ما إذا كان توقيع البيان الأصلي قد تحقّق بنجاح قبل التنقيح."
                    },
                    "leakage_warnings": {
                      "type": "integer",
                      "description": "دائمًا 0: التنقيح الذي يجد فحص ما بعد التنقيح فيه مصطلحًا متبقيًا يُرفض بالخطأ 422 REDACTION_LEAK_DETECTED بدلًا من إصداره. يُبقى عليه للتوافق."
                    },
                    "package_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "package_url_expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "verify_url": {
                      "type": "string",
                      "description": "رابط إلى أداة التحقق العامة من ProofSnap.",
                      "format": "uri"
                    },
                    "ignored_boxes": {
                      "type": "array",
                      "items": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "description": "فهارس (تبدأ من الصفر) في boxes الخاصة بالطلب للمربعات التي وقعت بالكامل خارج لقطة الشاشة ولم تطلِ شيئًا. تُسجَّل أيضًا في redaction_manifest.json (screenshot_boxes)."
                    },
                    "clipped_boxes": {
                      "type": "array",
                      "items": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "description": "فهارس (تبدأ من الصفر) في boxes الخاصة بالطلب للمربعات التي تجاوزت حافة لقطة الشاشة وقُصّت عندها. تُسجَّل أيضًا في redaction_manifest.json (screenshot_boxes)."
                    },
                    "screenshot_method": {
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "jpeg_dct_blocks",
                        "decode_repaint_jpeg",
                        null
                      ],
                      "description": "كيفية طلاء المربعات: jpeg_dct_blocks (استُبدلت كتل JPEG المضغوطة تحت كل مربع بكتل سوداء، وبقيت كل الكتل الأخرى دون تغيير) أو decode_repaint_jpeg (فُك ترميز لقطة الشاشة وطُليت وأُعيد ترميزها). null عندما لم يطلِ أي مربع شيئًا. تُسجَّل أيضًا في redaction_manifest.json (screenshot_boxes.method)."
                    },
                    "painted_rects": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "x": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "y": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "w": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "h": {
                            "type": "integer",
                            "minimum": 0
                          }
                        },
                        "required": [
                          "x",
                          "y",
                          "w",
                          "h"
                        ]
                      },
                      "description": "المناطق المسوّدة فعليًا، بالبكسل في لقطة الشاشة، واحدة لكل مربع مطبَّق بترتيب مربعات الطلب دون المربعات المتجاهَلة. مع jpeg_dct_blocks تكون كل منطقة هي المربع موسَّعًا إلى كتل كاملة، لذا تحتوي المربع؛ ومع decode_repaint_jpeg تكون المربع مقصوصًا ضمن حدود لقطة الشاشة وموسَّعًا إلى كتل كاملة 8 x 8 من الملف الجديد، تُسوَّد بعد إعادة الترميز كي لا ينقل الضغط ألوانًا مجاورة إلى داخل المربع. تُسجَّل أيضًا في redaction_manifest.json (screenshot_boxes.painted_rects)."
                    },
                    "text_rule_rects": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "مناطق متمايزة من لقطة الشاشة سُوِّدت لتنقيحات النص فقط، دون المناطق التي يشغلها مربع (المنطقة التي تطابقها قاعدتان تُحتسب مرة واحدة): في كتل النص من text_layout.json الخاص بالمصدر التي تطابق قاعدةٌ نصَّها، الأسطر المقدّرة التي تحتوي المطابقة، أو الكتلة كلها عندما يكون التقدير غير مؤكد. مُدرجة في redaction_manifest.json (screenshot_boxes.from_text_rules)."
                    },
                    "text_occurrences_not_located": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "مواضع ظهور تنقيحات النص في نص الصفحة التي تعذّر تحديد موقعها على لقطة الشاشة ولم تُسوَّد فيها (نص لا يدرجه text_layout.json الخاص بالمصدر، مثل نص داخل صورة؛ أو كتلة أكبر من أن تُسوَّد لأجل مصطلح واحد؛ أو مناطق تتجاوز حد التنقيح الواحد؛ أو لقطة شاشة تعذّر طلاؤها). لا تُحتسب هنا المواضع الواقعة في كتلة تقع بالكامل خارج لقطة الشاشة (لا شيء هناك ليُسوَّد)؛ إذ يدرجها redaction_manifest.json باسم occurrences_not_on_screenshot. لا يتجاوز هذا العدد أبدًا عدد المواضع في نص الصفحة. افحص لقطة الشاشة وأضف مربعات. يتضمن redaction_manifest.json (leakage_check.screenshot_text_advisory) الأعداد لكل قاعدة."
                    },
                    "text_variants_remaining": {
                      "type": "object",
                      "description": "إرشادي، وليس إخفاقًا أبدًا: لكل قاعدة نص حرفي، المواضع المتبقية في domtextcontent.txt وفي نص page.html في هذه الحزمة التي لم تطابقها القاعدة لكنها تطابق عند تجاهل حالة الأحرف وعلامات التشكيل (مثل «boskovické» للقاعدة الحساسة لحالة الأحرف «Boskovic»)، وما إذا كان المصطلح موجودًا في عنوان URL الخاص بعملية الالتقاط أو في عنوان URL النهائي، اللذين لا يُنقَّحان أبدًا. يجمع `total` لكل قاعدة القيمة الأكبر من العددين (يحتوي الملفان نص الصفحة نفسه). أعداد فقط، وليس النص أبدًا. لا تُحتسب قواعد التعبيرات النمطية. موجود أيضًا في redaction_manifest.json (leakage_check.text_variants_advisory). يظهر عندما يتضمن الطلب تنقيحات نصية.",
                      "properties": {
                        "total": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "per_rule": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "rule_index": {
                                "type": "integer",
                                "minimum": 0
                              },
                              "in_domtextcontent": {
                                "type": "integer",
                                "minimum": 0
                              },
                              "in_page_html_text": {
                                "type": "integer",
                                "minimum": 0
                              },
                              "in_capture_url": {
                                "type": "boolean"
                              }
                            }
                          }
                        }
                      }
                    },
                    "rules_in_capture_url": {
                      "type": "array",
                      "items": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "description": "يظهر عندما يتضمن الطلب تنقيحات نصية. فهارس (تبدأ من الصفر) قواعد النص التي يوجد نصها أيضًا في عنوان URL الخاص بعملية الالتقاط أو في عنوان URL النهائي. لا يتغير هذان العنوانان أبدًا: أُزيل النص من كل مكان آخر ويبقى ظاهرًا فيهما. فارغ عندما لا تطابقهما أي قاعدة. موجود أيضًا في redaction_manifest.json (leakage_check.capture_url، و`remains_in_capture_url: true` لكل قاعدة) وفي تقرير التنقيح."
                    },
                    "rule_commitment_salt": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{64}$",
                      "description": "يظهر عندما يتضمن الطلب تنقيحات نصية. الملح بطول 32 بايت (hex؛ وهو rule_commitment_salt من الطلب إذا أُرسل) لالتزامات rule_commitments في redaction_manifest.json: لكل قاعدة نصية، بصمة SHA-256 لملح القاعدة متبوعًا ببايتات UTF-8 لتمثيل JSON المعياري (canonical) للقاعدة {\"match\": <النص، أو {\"pattern\", \"flags\"}>, \"case_insensitive\": <bool>}، حيث ملح القاعدة هو HMAC-SHA256 بمفتاح هو هذا الملح على نص UTF-8 \"rule:<rule_index>\" (فيمكن إظهار قاعدة واحدة بالإفصاح عن ملحها وحده، دون غيرها). لا تُخزَّن القواعد، ولا يُعاد الملح إلا في هذه الاستجابة: لا في الاستجابة المُعادة عبر Idempotency-Key، ولا في webhook، ولا في سجل الأحداث، ولا في GET /captures/{capture_id}، ولا يحتفظ به ProofSnap. احفظه مع سجلاتك: به يمكنك إثبات القواعد التي طبّقها التنقيح."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (details.reason؛ مثلًا reason ليس نصًا عاديًا أو علم التعبير النمطي y)، أو REDACTION_NO_OPS (لم يُقدَّم شيء، أو لم يطابق شيء) أو INVALID_URL (webhook_url ليس https). WEBHOOK_REQUIRES_API_KEY: أُرسل webhook_url من جلسة لوحة التحكم (تتطلب رسائل webhook مفتاح API).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED أو PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "FORBIDDEN: عملية الالتقاط ليست لك. EIDAS_REQUIRES_LIVE_KEY: include_eidas مع مفتاح اختبار، أو لتنقيح عملية التقاط اختبارية (وحينها مع details.reason). EMAIL_NOT_VERIFIED: جلسة لوحة تحكم لم يُؤكَّد عنوان بريدها الإلكتروني. PLAN_ARCHIVE_ONLY: الخطة تحتفظ بالأرشيف فقط ولا تنشئ أي عمل جديد. PLAN_API_READ_ONLY: مفتاح API لحساب Website Archive (مفتاحه للقراءة فقط؛ استخدم لوحة التحكم). PLAN_API_READ_ONLY مع details.reason \"session_outside_dashboard\": جلسة لوحة تحكم لحساب Website Archive لا يحتوي طلبها على الترويسة Origin أو يحتوي على عنوان ليس عنوان لوحة تحكم ProofSnap (لا يُنشأ عمل جديد على هذه الخطة إلا في لوحة التحكم؛ وهو قيد مرن، وليس حدًا أمنيًا).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "REDACTION_NOT_READY: عملية الالتقاط المصدر لم تكتمل. IDEMPOTENCY_IN_PROGRESS: لا يزال طلب بمفتاح Idempotency-Key نفسه قيد التنفيذ (details.capture_id هو معرّف التنقيح الذي تستعلم به عبر GET /captures/{capture_id}). COMPANY_READ_ONLY: المصدر يعود إلى شركة محفوظة كأرشيف شركة.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "REDACTION_SCREENSHOT_UNREADABLE: بيانات صورة لقطة الشاشة غير مقروءة أو تتجاوز لقطة الشاشة حد تنقيح المربعات (150 ميغابكسل لملف JPEG بالشكل الذي تكتبه عملية الالتقاط، و60 ميغابكسل لملف PNG أو JPEG آخر)؛ يعمل تنقيح النص والبيانات الوصفية دون مربعات أيضًا. REDACTION_LEAK_DETECTED: بقي مصطلح منقّح خارج عنوان URL الخاص بعملية الالتقاط (details { files, rule_indexes, metadata_fields, in_capture_url }؛ تكون in_capture_url بالقيمة true عندما يوجد نص القاعدة المرفوضة أيضًا في عنوان URL الخاص بعملية الالتقاط أو في عنوان URL النهائي، حيث لا يُحتسب أبدًا). REDACTION_SOURCE_UNSUPPORTED: المصدر ليس عملية التقاط لصفحة ويب (details { source } أو { manifest_type }). SSRF_BLOCKED: يستهدف webhook_url عنوانًا محظورًا. REDACTION_SOURCE_INTEGRITY_FAILED: ملف من الحزمة المصدر لا يطابق بصمة SHA-256 المدرجة في بيانها الموقَّع، أو ملف مدرج مفقود (details { mismatched, missing }، أسماء الملفات فقط)؛ لم يُنقَّح أو يُحتسب أي شيء. IDEMPOTENCY_KEY_REUSED: سبق استخدام Idempotency-Key مع جسم طلب مختلف (أو من بيئة مفاتيح أخرى).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: طلبات كثيرة جدًا في النافذة الزمنية الحالية البالغة 60 ثانية. يكرر `error.details.retry_after_seconds` قيمة Retry-After. تُبلغ RateLimit بالقيمة r=0 وعدد الثواني حتى إعادة ضبط النافذة. وأيضًا RATE_LIMITED لجلسة لوحة تحكم Website Archive: حدها اليومي البالغ 100 وحدة لكل يوم UTC، المشترك بين عمليات الالتقاط الفردية والدفعية (وحدة واحدة لكل عنوان URL)، وcompare وredact وcertify-hash (وحدة واحدة لكل طلب)؛ والطلب الذي لا يتسع يُرفض بالكامل (details.daily_limit، details.remaining، details.action \"wa_session_captures\"؛ وتعطي Retry-After وdetails.retry_after_seconds عدد الثواني حتى يوم UTC التالي، وتُحذف ترويسات RateLimit وX-RateLimit-* الخاصة بالدقيقة). الرفض القصير الذي لا يحمل إلا details.retry_after_seconds وRetry-After: 1 يعني تحديثًا متزامنًا للعدّاد: أعد المحاولة. الرفض الذي لا يكون بسبب خطأ من المستدعي يعيد الوحدات؛ أما العمل المقبول بالرمز 202 الذي يفشل لاحقًا فيحتفظ بوحدته. وهو قيد مرن، وليس حدًا أمنيًا.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: خطأ غير متوقع في الخادم؛ أعد المحاولة مع تراجع أسي (exponential backoff). CAPTURE_TIME_BUDGET_EXCEEDED: استنفد التنقيح المتزامن ميزانيته الزمنية وأُوقف قبل إصدار أي حزمة؛ ولم يُحتسب أي شيء. عندما تكون قيمة `details.reason` هي `source_read`، فقد استغرقت قراءة الحزمة المصدر معظم الميزانية: الحزمة أكبر من أن تُنقَّح الآن، أعد المحاولة لاحقًا.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/compare": {
      "post": {
        "tags": [
          "المقارنة والتنقيح"
        ],
        "operationId": "compareCaptures",
        "summary": "مقارنة عمليتي التقاط",
        "description": "تقارن بين عمليتي التقاط مكتملتين لك (نص الصفحة، والصور المعروضة في الصفحة، والبيانات الوصفية) وتُنتج حزمة مقارنة موقَّعة. تعيد 202؛ استعلم عبر GET /captures/{compare_id}، الذي يحمل حينها type وcompared وdiff_summary. تُقارن الصور انطلاقًا من جرد الصور الذي ختمته كل عملية التقاط مصدر (image_inventory.json: عنوان كل صورة معروضة وبصمة SHA-256 لبايتاتها)، وليس بكسلًا ببكسل أبدًا؛ والنتائج في image_changes.json وفي diff_summary.image_changes. المصدر الملتقط قبل أن تبدأ عمليات الالتقاط بتسجيل جرد الصور يُقارن أيضًا، لكن دون مقارنة الصور (diff_summary.image_changes.status not_run). تُحتسب كعملية التقاط واحدة. المِثل بالمِثل (2026-10-14): تُرفض عمليتا الالتقاط المعروضتان بملف تعريف جهاز مختلف أو بإعدادات التقاط مختلفة بالخطأ 422 COMPARE_PROFILE_MISMATCH (details.differing، details.before، details.after: الحقول المختلفة فقط، وCSS وJavaScript المحقونان على شكل بصمة SHA-256 لكل منهما) ما لم تكن allow_profile_mismatch بالقيمة true، وتُرفض عمليتا التقاط لصفحتين مختلفتين بالخطأ 422 COMPARE_URL_MISMATCH (details.before وdetails.after: url وfinal_url على شكل origin ومسار) ما لم تكن allow_url_mismatch بالقيمة true. يجري الفحص أولًا على صفي عمليتي الالتقاط (قبل قراءة أي شيء أو حجزه)، ثم على metadata.json وforensic_log.json المختومين في كلتا الحزمتين ضمن فحص المصادر الموضح أدناه؛ وعدم التطابق الذي لا يُكتشف إلا أثناء تشغيل المقارنة يُفشل المقارنة بهذا الرمز ويعيد الوحدة الشهرية. يُقارن النص سطرًا بسطر بأداة فروق أسطر حتمية (دون مهلة): يحصي diff_summary الأسطر (text_unit line). لا يمكن مقارنة إلا عمليات التقاط الويب (المُجراة عبر API أو بجدول): يُرفض توثيق البصمة أو المقارنة أو الحزمة المنقّحة بالخطأ 422 COMPARE_SOURCE_UNSUPPORTED (`details.capture_id`، `details.source`) قبل قراءة أي حزمة وقبل حجز أي شيء. قبل مقارنة أي شيء أو حجزه، يُعاد حساب بصمة SHA-256 لكل ملف مدرج مع بصمة SHA-256 في manifest.json لكل حزمة مصدر؛ والمصدر الذي يحتوي ملفًا معدَّلًا أو مفقودًا، أو بيانًا لا يدرج أي ملف مع SHA-256، يُرفض بالخطأ 422 COMPARE_SOURCE_INTEGRITY_FAILED (لا مقارنة، ولا صف، ولا وحدة شهرية). لا تُقارن إلا تلك الملفات المدرجة: الملف الموجود داخل حزمة مصدر ولا يدرجه بيانها مع SHA-256 (مثل text_layout.json أو image_inventory.json أو page.html أو domtextcontent.txt مضاف) يُعامل كأنه غير موجود. يجري الفحص عند الطلب عندما يكون حجم كلتا الحزمتين المصدر 50 MB أو أقل وينتهي في نحو 10 ثوانٍ؛ وإلا يُقبل الطلب ويكرر تشغيل المقارنة الفحص، فيُفشل المصدرُ الذي لا يجتازه المقارنةَ عندئذ (يعرض GET /captures/{compare_id} الحالة failed مع error.code COMPARE_SOURCE_INTEGRITY_FAILED) ويعيد الوحدة الشهرية. تسجّل المقارنة المكتملة هذا الفحص في compare.json `sources[].source_files_reverified` وفي comparison_method.json `source_verification`. لا يرسل /compare رسائل webhook: يُتجاهل webhook_url في جسم الطلب. استعلم عبر GET /captures/{compare_id}.\n\nالمقارنة التي تشمل عملية التقاط اختبارية (أُجريت بمفتاح اختبار، أو في وضع الاختبار (sandbox)، أو بحساب غير مرتبط بحساب فوترة) هي حزمة اختبارية أيًّا كان المفتاح الذي يطلبها: بعلامة مائية، و`test_mode: true` في manifest.json، و`test_mode: true` لذلك المصدر في compare.json `sources[]`، وصف اختباري (key_env test) لا يُحتسب ولا يستهلك وحدة شهرية. يُرفض include_eidas مع مصدر اختباري بالخطأ 403 EIDAS_REQUIRES_LIVE_KEY (details.reason).\n\nيُطبَّق حد المعدّل على كل حساب في نوافذ زمنية مدتها 60 ثانية (تتشارك كل مفاتيح API في الحساب وجلسة لوحة التحكم الخاصة به نافذة واحدة). بعد تشغيل المحدِّد، تتضمن استجابات هذا المسار الترويستين `RateLimit-Policy` و`RateLimit` (استنادًا إلى IETF draft-ietf-httpapi-ratelimit-headers) إضافة إلى الترويسات القديمة `X-RateLimit-Limit` و`X-RateLimit-Remaining` و`X-RateLimit-Reset`. وتضيف الاستجابة 429 الترويسة `Retry-After`.\n\nأرسل Idempotency-Key كي تعيد إعادة المحاولة (مثلًا بعد 504) الاستجابة الأولى بدلًا من تشغيل ثانٍ مفوتر (24 ساعة، القواعد نفسها المطبقة على POST /captures). يُحجز المفتاح قبل فحص المصادر، لذا تعيد إعادة المحاولة أثناء استمرار الطلب الأول 409 IDEMPOTENCY_IN_PROGRESS؛ والطلب الذي يرفضه فحص المصادر أو الحصة يحرّر مفتاحه، فتُفحص إعادة المحاولة بالمفتاح نفسه من جديد.\n\nلا يمكن مقارنة حزمة أدلة مستوردة (source import، معرّفات تبدأ بـ `imp_`): 422 IMPORT_NOT_SUPPORTED (details.capture_id).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "capture_id_1": {
                    "type": "string",
                    "description": "عملية الالتقاط الأقدم."
                  },
                  "capture_id_2": {
                    "type": "string",
                    "description": "عملية الالتقاط الأحدث. يجب أن تختلف عن capture_id_1."
                  },
                  "language": {
                    "type": "string",
                    "description": "لغة ملف PDF الخاص بالمقارنة. يُقبل الرمز الدقيق فقط؛ وأي قيمة أخرى تعود بصمت إلى الإنجليزية.",
                    "enum": [
                      "en",
                      "de",
                      "it",
                      "fr",
                      "es",
                      "pt_BR",
                      "cs",
                      "el",
                      "hi",
                      "hu",
                      "id",
                      "ja",
                      "ko",
                      "nl",
                      "pl",
                      "ro",
                      "ru",
                      "sv",
                      "tr",
                      "vi",
                      "zh_CN",
                      "zh_TW",
                      "ar",
                      "he",
                      "da",
                      "fi",
                      "nb",
                      "sk",
                      "uk"
                    ]
                  },
                  "include_eidas": {
                    "type": "boolean",
                    "description": "كما في CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "include_rule_902": {
                    "type": "boolean",
                    "description": "يضيف قالب إقرار غير موقّع (rule_902_certification.pdf، انظر CaptureOptions.include_rule_902) إلى المقارنة. غير متاح لعمليات الالتقاط التي عُرضت بشكل مختلف: مع allow_profile_mismatch true ووجود اختلاف في ملف تعريف الجهاز أو الإعدادات تُعاد 422 RULE902_NOT_AVAILABLE_FOR_MISMATCH. معطّل حاليًا: قالب الإقرار متوقف مؤقتًا، لذا يُقبل هذا الحقل ويُتجاهل (لا يُنشأ rule_902_certification.pdf ولا يُدرج في الحزمة) وتحمل الاستجابة التحذير RULE902_DISABLED.",
                    "default": false
                  },
                  "allow_profile_mismatch": {
                    "type": "boolean",
                    "default": false,
                    "description": "يقارن بين عمليتي التقاط حتى لو أُجريتا بملف تعريف جهاز مختلف (options.viewport mobile أو desktop، viewport_width، viewport_height، device_scale_factor؛ وعلى المستوى المختوم: حجم منطقة العرض، ومعامل تحجيم الجهاز، ومحاكاة الهاتف المحمول، ووكيل المستخدم دون أرقام الإصدارات) أو بإعدادات التقاط مختلفة تغيّر طريقة عرض الصفحة (dark_mode، block_ads، block_resources، inject_css، inject_js حسب SHA-256، expand_collapsed، dismiss_consent، dismiss_popups، الصفحة الكاملة). بدونه يُرفض هذا الزوج بالخطأ 422 COMPARE_PROFILE_MISMATCH. ومعه تكون diff_summary.text_verdict وimages_verdict بقيمة undetermined، وتكون changed بقيمة null ما لم يختلف أحد حقول البيانات الوصفية للمحتوى، ويُرفض include_rule_902."
                  },
                  "allow_url_mismatch": {
                    "type": "boolean",
                    "default": false,
                    "description": "يقارن بين عمليتي التقاط لصفحتين مختلفتين. تُعدّ عمليتا الالتقاط للصفحة نفسها عندما يساوي أحد عناوين الأولى (url المطلوب، وurl وfinal_url اللذان يسجلهما ملف metadata.json الخاص بها) أحد عناوين الثانية بعد التطبيع (اسم المضيف بأحرف صغيرة، وhttp وhttps متماثلان، مع إغفال المنفذ الافتراضي والجزء (fragment) والشرطة المائلة الختامية ومعلمات الاستعلام المتغيرة). بدونه تُرفض الصفحات المختلفة بالخطأ 422 COMPARE_URL_MISMATCH."
                  }
                },
                "required": [
                  "capture_id_1",
                  "capture_id_2"
                ]
              },
              "example": {
                "capture_id_1": "cap_a1B2c3D4e5F6g7H8",
                "capture_id_2": "cap_h8G7f6E5d4C3b2A1"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "مقبول.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/QuotaLimit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/QuotaRemaining"
              },
              "X-Quota-Used": {
                "$ref": "#/components/headers/QuotaUsed"
              },
              "X-Quota-Cap": {
                "$ref": "#/components/headers/QuotaCap"
              },
              "X-Quota-Org-Limit": {
                "$ref": "#/components/headers/QuotaOrgLimit"
              },
              "X-Quota-Org-Used": {
                "$ref": "#/components/headers/QuotaOrgUsed"
              },
              "X-Sandbox-Limit": {
                "$ref": "#/components/headers/SandboxLimit"
              },
              "X-Sandbox-Remaining": {
                "$ref": "#/components/headers/SandboxRemaining"
              },
              "X-Sandbox-Used": {
                "$ref": "#/components/headers/SandboxUsed"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "compare_id": {
                      "type": "string",
                      "example": "cmp_a1B2c3D4e5F6g7H8"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "poll_path": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "warnings": {
                      "$ref": "#/components/schemas/Rule902DisabledWarnings"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: JSON غير صالح، أو معرّف مفقود، أو المعرّف نفسه مرتين، أو قيمة allow_profile_mismatch / allow_url_mismatch ليست قيمة منطقية (boolean).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED أو PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "FORBIDDEN: عملية الالتقاط ليست ملكك. EIDAS_REQUIRES_LIVE_KEY: include_eidas مع مفتاح اختبار، أو مع عملية التقاط مصدر في وضع الاختبار (وحينها مع details.reason). EMAIL_NOT_VERIFIED: جلسة في لوحة التحكم لم يُؤكَّد عنوان بريدها الإلكتروني. PLAN_ARCHIVE_ONLY: الخطة تحتفظ بالأرشيف فقط ولا تنشئ أي عمل جديد. PLAN_API_READ_ONLY: مفتاح API لحساب Website Archive (مفتاحه للقراءة فقط؛ استخدم لوحة التحكم). PLAN_API_READ_ONLY مع details.reason \"session_outside_dashboard\": جلسة في لوحة التحكم لحساب Website Archive لا يحتوي طلبها على الترويسة Origin أو يحتوي عليها بعنوان ليس عنوان لوحة تحكم ProofSnap (يُنشأ العمل الجديد على هذه الخطة في لوحة التحكم فقط؛ وهذا قيد مرن وليس حدًا أمنيًا).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "COMPARE_NOT_READY: يجب أن تكون عمليتا الالتقاط مكتملتين (completed). COMPANY_READ_ONLY: ينتمي أحد المصدرين إلى شركة محفوظة كأرشيف شركة (لا يُنشأ منه أي شيء جديد).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "COMPARE_SOURCE_INTEGRITY_FAILED: ملف من عملية التقاط مصدر لا يطابق بصمة SHA-256 المدرجة في بيانها الموقّع، أو أنه مفقود؛ لم تُجرَ أي مقارنة ولم يُحتسب أي رسم. تحدد `details.capture_id` و`details.mismatched` و`details.missing` أول مصدر فشل في التحقق؛ وتظهر `details.reason` عندما يتعذر استخدام بيان المصدر نفسه (مثلًا \"the source manifest lists no file with a SHA-256\" أو \"the source manifest lists no files\")؛ وتسرد `details.sources[]` كل مصدر فشل بالحقول نفسها. تحقّق من عملية الالتقاط المصدر (POST /captures/{capture_id}/verify). COMPARE_SOURCE_UNSUPPORTED: المصدر ليس عملية التقاط ويب (بل توثيق بصمة أو مقارنة أو حزمة منقّحة)؛ لم يُقرأ شيء ولم تُجرَ مقارنة ولم يُحتسب رسم. تحدد `details.capture_id` هذا المصدر وتحدد `details.source` نوعه عندما يسجله الصف. قارن عمليات الالتقاط الأصلية للويب بدلًا من ذلك. COMPARE_PROFILE_MISMATCH: أُجريت عمليتا الالتقاط بملف تعريف جهاز مختلف أو بإعدادات التقاط مختلفة؛ تسرد `details.differing` أسماء الحقول (على مستوى الصف: viewport، viewport_width، viewport_height، device_scale_factor، dark_mode، block_ads، block_resources، inject_css، inject_js، expand_collapsed، dismiss_consent، dismiss_popups، full_page؛ وعلى المستوى المختوم: viewport_width، viewport_height، device_scale_factor، is_mobile، user_agent والإعدادات نفسها)، وتعطي `details.before` و`details.after` قيمها. أرسل allow_profile_mismatch: true للمقارنة رغم ذلك. COMPARE_URL_MISMATCH: عمليتا الالتقاط لصفحتين مختلفتين؛ تعطي `details.before` و`details.after` قيمتي url وfinal_url في صورة الأصل (origin) والمسار. أرسل allow_url_mismatch: true للمقارنة رغم ذلك. RULE902_NOT_AVAILABLE_FOR_MISMATCH: include_rule_902 مع allow_profile_mismatch لعمليات التقاط عُرضت بشكل مختلف؛ `details.differing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: طلبات كثيرة جدًا في النافذة الزمنية الحالية البالغة 60 ثانية. يكرر `error.details.retry_after_seconds` قيمة Retry-After. تُبلغ RateLimit بالقيمة r=0 وعدد الثواني حتى إعادة ضبط النافذة. وأيضًا RATE_LIMITED لجلسة لوحة تحكم Website Archive: حدها اليومي البالغ 100 وحدة لكل يوم UTC، المشترك بين عمليات الالتقاط الفردية والدفعية (وحدة واحدة لكل عنوان URL)، وcompare وredact وcertify-hash (وحدة واحدة لكل طلب)؛ والطلب الذي لا يتسع يُرفض بالكامل (details.daily_limit، details.remaining، details.action \"wa_session_captures\"؛ وتعطي Retry-After وdetails.retry_after_seconds عدد الثواني حتى يوم UTC التالي، وتُحذف ترويسات RateLimit وX-RateLimit-* الخاصة بالدقيقة). الرفض القصير الذي لا يحمل إلا details.retry_after_seconds وRetry-After: 1 يعني تحديثًا متزامنًا للعدّاد: أعد المحاولة. الرفض الذي لا يكون بسبب خطأ من المستدعي يعيد الوحدات؛ أما العمل المقبول بالرمز 202 الذي يفشل لاحقًا فيحتفظ بوحدته. وهو قيد مرن، وليس حدًا أمنيًا.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/certify-hash": {
      "post": {
        "tags": [
          "توثيق البصمة"
        ],
        "operationId": "certifyHash",
        "summary": "توثيق بصمات الملفات",
        "description": "أرسل بصمة SHA-256 واحدة (hash) أو ما يصل إلى 100 بصمة (hashes). تحتوي الحزمة على manifest.json (مع البصمات الخاصة بك) وmanifest.sig وpublickey.pem وevidence.pdf والبرامج النصية في verification/، وعند توفره manifest.json.ots.\n\n- بصمة واحدة: متزامن، ويعيد 200 مع الشهادة الجاهزة.\n- عدة بصمات مع combined: true: شهادة واحدة تسرد كل البصمات، متزامن، ويعيد 200.\n- عدة بصمات بدون combined: شهادة مستقلة لكل بصمة، يبدأ إنشاؤها في الخلفية؛ ويعيد 202 مع certify_ids. استعلم دوريًا عبر GET /captures/{certify_id}؛ ومع webhook_url ترسل كل شهادة الحدث certify.completed.\n\nتُحتسب كل شهادة عملية التقاط واحدة لأغراض قياس الاستخدام والحد الشهري وحد المعدّل (N بصمة بدون combined تُحتسب N؛ وإذا كان N أكبر من الحد المسموح في النافذة الزمنية الواحدة تُعاد 400 RATE_LIMIT_COST_TOO_HIGH). يُطبع `label` على الشهادة: ويُقتطع إلى 200 حرف ويجب أن يكون نصًا عاديًا (دون أحرف تحكم أو أحرف غير مرئية أو أحرف اتجاه النص، وإلا تُعاد 400). في الدفعة، يحدد الخطأ المتعلق بعنصر واحد هذا العنصر في `error.details.index`.\n\nيُطبَّق حد المعدّل لكل حساب في نوافذ زمنية مدتها 60 ثانية (تتشارك كل مفاتيح API الخاصة بالحساب وجلسة لوحة التحكم الخاصة به نافذة واحدة). بعد مرور الطلب عبر المحدِّد، تتضمن الاستجابات من هذا المسار الترويستين `RateLimit-Policy` و`RateLimit` (استنادًا إلى مسودة IETF draft-ietf-httpapi-ratelimit-headers) إضافة إلى الترويسات القديمة `X-RateLimit-Limit` و`X-RateLimit-Remaining` و`X-RateLimit-Reset`. وتضيف الاستجابة 429 الترويسة `Retry-After`.\n\nأرسل Idempotency-Key كي تعيد المحاولة المتكررة (مثلًا بعد 504) الاستجابة الأولى بدلًا من تشغيل ثانٍ مدفوع (24 ساعة، بالقواعد نفسها المطبقة على POST /captures).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hash": {
                    "type": "string",
                    "description": "بصمة SHA-256 في صورة 64 حرفًا ست عشريًا (دون تمييز بين الأحرف الكبيرة والصغيرة). استخدم hash أو hashes."
                  },
                  "label": {
                    "type": "string",
                    "description": "الاسم المعروض للملف. تُختصر القيم الأطول إلى 200 حرف."
                  },
                  "hashes": {
                    "type": "array",
                    "items": {
                      "oneOf": [
                        {
                          "type": "string",
                          "description": "بصمة SHA-256 بالصيغة الست عشرية."
                        },
                        {
                          "type": "object",
                          "properties": {
                            "hash": {
                              "type": "string"
                            },
                            "label": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "hash"
                          ]
                        }
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 100
                  },
                  "combined": {
                    "type": "boolean",
                    "description": "توثيق كل البصمات في شهادة واحدة.",
                    "default": false
                  },
                  "algorithm": {
                    "type": "string",
                    "enum": [
                      "sha256"
                    ],
                    "default": "sha256"
                  },
                  "language": {
                    "type": "string",
                    "description": "لغة ملفات PDF المخصصة للقراءة البشرية. إحدى القيم: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. يُتسامح مع حالة الأحرف والشرطة والمنطقة (تُقبل \"DE\" و\"fr-FR\" و\"PT-BR\")؛ وتُرفض \"pt\" و\"zh\" المجردتان. تعيد القيمة غير المدعومة 400 MALFORMED_REQUEST."
                  },
                  "case_reference": {
                    "type": "string",
                    "description": "يُطبع على الشهادة. يخضع لقاعدة النص العادي (انظر PlainText)؛ بحد أقصى 200 حرف.",
                    "maxLength": 200
                  },
                  "include_eidas": {
                    "type": "boolean",
                    "description": "كما في CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "include_rule_902": {
                    "type": "boolean",
                    "description": "يضيف قالب إقرار غير موقّع (rule_902_certification.pdf، انظر CaptureOptions.include_rule_902). معطّل حاليًا: قالب الإقرار متوقف مؤقتًا، لذا يُقبل هذا الحقل ويُتجاهل (لا يُنشأ rule_902_certification.pdf ولا يُدرج في الحزمة) وتحمل الاستجابة التحذير RULE902_DISABLED.",
                    "default": false
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "عنوان URL بـ https يستقبل certify.completed. يجب أن يستخدم العنوان https وألا يشير إلى عنوان خاص أو loopback أو link-local أو carrier-grade NAT أو عنوان بيانات وصفية سحابية (القواعد نفسها المطبقة على عنوان URL لعملية الالتقاط؛ وإلا تُعاد 422 SSRF_BLOCKED). القيمة الفارغة أو المكونة من مسافات فقط تعني عدم وجود webhook. يتطلب مفتاح API: إذا أُرسل من جلسة في لوحة التحكم يُرفض بالخطأ 400 WEBHOOK_REQUIRES_API_KEY، لأن رسائل webhook توقَّع بسر webhook الخاص بالمفتاح.",
                    "format": "uri"
                  }
                }
              },
              "example": {
                "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                "label": "contract.pdf"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "الشهادة جاهزة (لبصمة واحدة، أو مجمّعة).",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CertifyResult"
                }
              }
            }
          },
          "202": {
            "description": "بدأ إنشاء عدة شهادات مستقلة. الشهادة التي يرفضها الحد الشهري أو حصة وضع الاختبار (sandbox) عند تشغيلها تفشل: يكون لصفها (GET /captures/{certify_id}) الحالة status failed والقيمة error.code QUOTA_EXCEEDED.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "batch_id": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "certify_ids": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "معرّفات الشهادات التي أُنشئت (بما فيها الشهادة التي فشل بدؤها)."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "certificates": {
                      "type": "array",
                      "description": "إدخال واحد لكل بصمة مُرسلة، بالترتيب. status failed: تعذّر بدء هذه الشهادة (ويُعلَّم صفها بأنه failed إن كان قد أُنشئ)؛ وتستمر الشهادات الأخرى في العمل.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "certify_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "hash_sha256": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "processing",
                              "failed"
                            ]
                          }
                        }
                      }
                    },
                    "failed_count": {
                      "type": "integer",
                      "description": "عدد إدخالات certificates التي فشل بدؤها. عندما يفشل بدء جميعها تكون الاستجابة بدلًا من ذلك 500 CONFIGURATION_ERROR."
                    },
                    "warnings": {
                      "$ref": "#/components/schemas/Rule902DisabledWarnings"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (تذكر details.reason الحقل، مثلًا \"label must be a string\"؛ وتحدد details.index عنصر الدفعة)، أو INVALID_HASH (details.index في الدفعة) أو INVALID_URL (webhook_url ليس https، أو يتضمن بيانات اعتماد). WEBHOOK_REQUIRES_API_KEY: أُرسل webhook_url من جلسة في لوحة التحكم (يتطلب webhook مفتاح API). RATE_LIMIT_COST_TOO_HIGH: عدد البصمات (بدون combined) أكبر مما تسمح به نافذة زمنية واحدة لحد المعدّل؛ details { max_per_window, window_seconds, requested }.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED، أو QUOTA_EXCEEDED في المسارات المتزامنة.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "EIDAS_REQUIRES_LIVE_KEY (details.reason \"no_billing_account\" عندما يعمل مفتاح فعلي أو جلسة بوصفهما في وضع الاختبار لعدم ربط حساب فوترة). EMAIL_NOT_VERIFIED: جلسة في لوحة التحكم لم يُؤكَّد عنوان بريدها الإلكتروني، أو مفتاح API لحساب كهذا. PLAN_ARCHIVE_ONLY: الخطة تحتفظ بالأرشيف فقط ولا تنشئ أي عمل جديد. PLAN_API_READ_ONLY: مفتاح API لحساب Website Archive (مفتاحه للقراءة فقط؛ استخدم لوحة التحكم). PLAN_API_READ_ONLY مع details.reason \"session_outside_dashboard\": جلسة في لوحة التحكم لحساب Website Archive لا يحتوي طلبها على الترويسة Origin أو يحتوي عليها بعنوان ليس عنوان لوحة تحكم ProofSnap (يُنشأ العمل الجديد على هذه الخطة في لوحة التحكم فقط؛ وهذا قيد مرن وليس حدًا أمنيًا).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED: يستهدف webhook_url عنوانًا خاصًا أو loopback أو link-local أو عنوانًا محظورًا بطريقة أخرى.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: طلبات كثيرة جدًا في النافذة الزمنية الحالية البالغة 60 ثانية. يكرر `error.details.retry_after_seconds` قيمة Retry-After. تُبلغ RateLimit بالقيمة r=0 وعدد الثواني حتى إعادة ضبط النافذة. وأيضًا RATE_LIMITED لجلسة لوحة تحكم Website Archive: حدها اليومي البالغ 100 وحدة لكل يوم UTC، المشترك بين عمليات الالتقاط الفردية والدفعية (وحدة واحدة لكل عنوان URL)، وcompare وredact وcertify-hash (وحدة واحدة لكل طلب)؛ والطلب الذي لا يتسع يُرفض بالكامل (details.daily_limit، details.remaining، details.action \"wa_session_captures\"؛ وتعطي Retry-After وdetails.retry_after_seconds عدد الثواني حتى يوم UTC التالي، وتُحذف ترويسات RateLimit وX-RateLimit-* الخاصة بالدقيقة). الرفض القصير الذي لا يحمل إلا details.retry_after_seconds وRetry-After: 1 يعني تحديثًا متزامنًا للعدّاد: أعد المحاولة. الرفض الذي لا يكون بسبب خطأ من المستدعي يعيد الوحدات؛ أما العمل المقبول بالرمز 202 الذي يفشل لاحقًا فيحتفظ بوحدته. وهو قيد مرن، وليس حدًا أمنيًا.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "CONFIGURATION_ERROR: فشل التوثيق (details.reason).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          },
          "504": {
            "description": "UPSTREAM_TIMEOUT: لم يكتمل التوثيق المتزامن (لبصمة واحدة، أو المجمّع) خلال نحو 25 ثانية. قد يكتمل لاحقًا: تحدد details.certify_id وdetails.poll_path الصف الذي يجب الاستعلام عنه دوريًا عبر GET /captures/{capture_id}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/webhook-events/{capture_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookEvents",
        "summary": "سجل تسليم webhook",
        "description": "كل محاولة تسليم لعملية التقاط أو شهادة أو تنقيح واحد، بدءًا من الأقدم.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "سجل التسليم.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "capture_id": {
                      "type": "string"
                    },
                    "webhook_url": {
                      "type": "string",
                      "description": "عنوان webhook الخاص بالصف (options.webhook_url لعملية الالتقاط، أو العنوان المُعطى مع الشهادة أو التنقيح؛ وnull عند عدم وجوده). لمالك الشركة التي أُجريت عملية الالتقاط لصالحها، عند قراءة سجل أحد المقاعد، يظهر فقط المخطط (scheme) والمضيف والمنفذ والمسار (يُحذف الاستعلام والجزء (fragment) لأنهما قد يحملان رمزًا مميزًا)؛ وتُحفظ هذه القراءة في سجل الوصول إلى عملية الالتقاط (action webhook_events).",
                      "nullable": true
                    },
                    "attempts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "attempt_number": {
                            "type": "integer"
                          },
                          "attempted_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "status_code": {
                            "type": "integer",
                            "nullable": true
                          },
                          "latency_ms": {
                            "type": "integer",
                            "nullable": true
                          },
                          "success": {
                            "type": "boolean"
                          },
                          "error": {
                            "type": "string",
                            "nullable": true,
                            "description": "null عند نجاح التسليم. وإلا فئة عامة، وليس خطأ الشبكة الخام أبدًا: connection_failed، timeout، blocked_address (حُلّ اسم المضيف إلى عنوان خاص أو محظور بطريقة أخرى وقت التسليم)، redirect_not_followed (استجابت نقطة النهاية (endpoint) بـ 3xx؛ لا تُتبع عمليات إعادة التوجيه أبدًا)، http_<status> (استجابة خارج نطاق 2xx)، أو جملة توضح سبب عدم إرسال الحدث (لا يوجد مفتاح API، أو المفتاح مُبطَل أو غير موجود، أو لا يوجد سر webhook، أو فشل البحث عن السر)."
                          },
                          "delivery_id": {
                            "type": "string",
                            "description": "الترويسة webhook-id لهذا التسليم دون البادئة `msg_`."
                          }
                        }
                      }
                    },
                    "attempt_count": {
                      "type": "integer"
                    },
                    "last_success": {
                      "type": "string",
                      "description": "قيمة attempted_at لآخر محاولة ناجحة.",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: عملية الالتقاط ليست لك.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/scheduled-captures": {
      "post": {
        "tags": [
          "عمليات الالتقاط المجدولة"
        ],
        "operationId": "createSchedule",
        "summary": "إنشاء جدول زمني",
        "description": "interval وcron: يُجري كل تشغيل عملية التقاط، ومع webhook_url يرسل capture.completed أو capture.failed.\n\non_change: يجلب كل تشغيل الصفحة أولًا (عبر HTTP العادي، أو بعرضها في متصفح مع js_render) ويحسب البصمة لنصها المُطبَّع. هذا الفحص ليس عملية التقاط ولا يُحتسب عليه رسم. يُجري التشغيل الأول عملية التقاط مرجعية. عندما تتغير البصمة، تُجرى عملية التقاط جديدة وتُقارن بالسابقة، ويُرسل monitor.change_detected إلى webhook_url. الصفحة التي تعيد خطأ أو لا تكاد تحتوي على نص تُعدّ بلا تغيير.\n\nبعد 5 عمليات تشغيل فاشلة متتالية يُوقِف الجدول نفسَه مؤقتًا. عند بلوغ الحد الشهري أو تأخر سداد الاشتراك، تُتخطى عمليات التشغيل ويبقى الجدول نشطًا.\n\nالحدود حسب الخطة:\n\n| الخطة | الجداول النشطة | الحد الأدنى للتواتر | on_change |\n| --- | --- | --- | --- |\n| sandbox | 0 | غير متاح | لا |\n| developer | 3 | 300 ثانية | نعم |\n| startup | 25 | 300 ثانية | نعم |\n| growth | 200 | 300 ثانية | نعم |\n| custom | غير محدود | 300 ثانية | نعم |\n| legal_solo | 3 | 300 ثانية | نعم |\n| legal_seats | 3 | 300 ثانية | نعم |\n| website_archive | 30 | 86400 ثانية | نعم |\n| archive_only | 0 | غير متاح | لا |\n\nIdempotency-Key: المحاولة المتكررة بالمفتاح نفسه والجسم نفسه خلال 24 ساعة تعيد استجابة 201 الأولى (الترويسة Idempotent-Replay: true) بدلًا من إنشاء جدول ثانٍ؛ والمفتاح نفسه مع جسم مختلف يعيد 422 IDEMPOTENCY_KEY_REUSED.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScheduleRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "تم الإنشاء.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST أو INVALID_URL أو INVALID_CRON أو INTERVAL_TOO_SHORT (details.min_interval_seconds) أو خطأ في خيارات عملية الالتقاط. WEBHOOK_REQUIRES_API_KEY: أُرسل webhook_url من جلسة في لوحة التحكم (يتطلب webhook مفتاح API).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "SCHEDULE_LIMIT_REACHED (details.max_active، details.active) أو ON_CHANGE_REQUIRES_PLAN. EMAIL_NOT_VERIFIED: جلسة في لوحة التحكم لم يُؤكَّد عنوان بريدها الإلكتروني. PLAN_ARCHIVE_ONLY: الخطة تحتفظ بالأرشيف فقط ولا تنشئ أي عمل جديد. PLAN_API_READ_ONLY: مفتاح API لحساب Website Archive (مفتاحه للقراءة فقط؛ استخدم لوحة التحكم). PLAN_API_READ_ONLY مع details.reason \"session_outside_dashboard\": جلسة في لوحة التحكم لحساب Website Archive لا يحتوي طلبها على الترويسة Origin أو يحتوي عليها بعنوان ليس عنوان لوحة تحكم ProofSnap (يُنشأ العمل الجديد على هذه الخطة في لوحة التحكم فقط؛ وهذا قيد مرن وليس حدًا أمنيًا).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED: يستهدف url أو webhook_url عنوانًا خاصًا أو loopback أو link-local أو عنوانًا محظورًا بطريقة أخرى.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      },
      "get": {
        "tags": [
          "عمليات الالتقاط المجدولة"
        ],
        "operationId": "listSchedules",
        "summary": "عرض الجداول الزمنية",
        "description": "ما يصل إلى 100 من جداولك الزمنية.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "الجداول الزمنية.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schedules": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Schedule"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/scheduled-captures/{schedule_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ScheduleId"
        }
      ],
      "get": {
        "tags": [
          "عمليات الالتقاط المجدولة"
        ],
        "operationId": "getSchedule",
        "summary": "جلب جدول زمني",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "الجدول الزمني.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "عمليات الالتقاط المجدولة"
        ],
        "operationId": "updateSchedule",
        "summary": "إيقاف جدول زمني مؤقتًا أو استئنافه أو تعديله",
        "description": "أرسل status للإيقاف المؤقت أو الاستئناف، و/أو أي حقل من ScheduleRequest للتعديل. الحقل ذو المستوى الأعلى الذي ترسله يحل محل القيمة المخزنة، بما في ذلك null الصريحة (بهذه الطريقة تُمسح webhook_url وtimezone وjs_render)؛ والحقل الذي لا ترسله يحتفظ بقيمته المخزنة؛ ثم يُتحقق من النتيجة مجددًا ككل. `options` استثناء من التعديل حقلًا بحقل: إذ تُستبدل كخريطة كاملة ولا تُدمج مفتاحًا بمفتاح، لذا أرسل options الكاملة التي تريدها (اقرأها أولًا عبر GET). الخيار الذي تغفله من الخريطة يُحذف. يتحقق الاستئناف من حد الجداول النشطة، ويصفّر عدّاد الإخفاقات، ويحسب التشغيل التالي بدءًا من الآن.\n\nمن جلسة في لوحة التحكم لا يمكن تعيين webhook_url (400 WEBHOOK_REQUIRES_API_KEY)؛ وإرسال null أو سلسلة فارغة يمسحه. الجدول المُنشأ من جلسة في لوحة التحكم لم يكن له قط مفتاح API للتوقيع به، لذا يؤدي تعديله أيضًا إلى حذف أي webhook_url خُزّن عليه سابقًا، ولا يستطيع مفتاح API إضافة واحد إليه كذلك (400 WEBHOOK_REQUIRES_API_KEY): إذ يوقّع المجدوِل رسائل webhook الخاصة بالجدول بسر webhook للمفتاح الذي أُنشئ به الجدول. لتلقي رسائل webhook، أنشئ الجدول بمفتاح API.\n\nتغيير url أو mode أو js_render لجدول on_change (أو تحويل جدول إلى on_change أو منه) يمسح خط الأساس الخاص به (بصمة الفحص المخزنة وآخر عملية التقاط)، فيُجري التشغيل التالي عملية التقاط مرجعية جديدة بدلًا من مقارنة الهدف الجديد بالقديم. يجب أن يكون جسم الطلب كائن JSON (وإلا 400 MALFORMED_REQUEST).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "paused"
                    ]
                  },
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "interval",
                      "cron",
                      "on_change"
                    ]
                  },
                  "interval_seconds": {
                    "type": "integer"
                  },
                  "cron_expr": {
                    "type": "string"
                  },
                  "timezone": {
                    "type": "string",
                    "nullable": true,
                    "description": "المنطقة الزمنية وفق IANA لـ cron. القيمة null أو السلسلة الفارغة تعيدها إلى UTC."
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "أرسل null أو سلسلة فارغة لإزالته. يجب أن يستخدم العنوان https وألا يشير إلى عنوان خاص أو loopback أو link-local أو carrier-grade NAT أو عنوان بيانات وصفية سحابية (القواعد نفسها المطبقة على عنوان URL لعملية الالتقاط؛ وإلا 422 SSRF_BLOCKED). يتطلب مفتاح API في الجانبين: إذا أُرسل من جلسة في لوحة التحكم، أو أُرسل بمفتاح API لجدول زمني أُنشئ في لوحة التحكم، يُرفض بالخطأ 400 WEBHOOK_REQUIRES_API_KEY، لأن رسائل webhook توقَّع بسر webhook للمفتاح الذي أُنشئ به الجدول.",
                    "nullable": true
                  },
                  "js_render": {
                    "type": "boolean",
                    "nullable": true,
                    "description": "null تعني false."
                  },
                  "options": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/CaptureOptions"
                      }
                    ],
                    "description": "يستبدل الخيارات المخزنة ككل (دون دمجها مفتاحًا بمفتاح). أرسل الخريطة كاملة؛ والخيار المُغفل يُحذف. القيمة null تعيد الخيارات إلى قيمها الافتراضية. لا يُخزَّن options.webhook_url أبدًا (انظر webhook_url)."
                  },
                  "max_capture_age_seconds": {
                    "type": "integer",
                    "nullable": true,
                    "minimum": 86400,
                    "maximum": 34560000,
                    "description": "لـ on_change فقط: عملية التقاط كاملة مرة واحدة على الأقل كل N ثانية حتى دون تغيير (من يوم واحد إلى 400 يوم، وأطول من interval_seconds). القيمة null تزيله. تحويل الوضع إلى interval أو cron يسقطه."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "الجدول الزمني المحدّث.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (بما في ذلك عدم وجود ما يُحدَّث) أو INVALID_URL أو INVALID_CRON أو INTERVAL_TOO_SHORT. WEBHOOK_REQUIRES_API_KEY: أُرسل webhook_url من جلسة في لوحة التحكم، أو أُرسل بمفتاح API لجدول زمني أُنشئ في لوحة التحكم (ليس له مفتاح يمكن لسر webhook الخاص به توقيع رسائل webhook).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "SCHEDULE_LIMIT_REACHED أو ON_CHANGE_REQUIRES_PLAN. EMAIL_NOT_VERIFIED: جلسة في لوحة التحكم لم يُؤكَّد عنوان بريدها الإلكتروني (عند استئناف جدول زمني متوقف مؤقتًا؛ ويبقى الإيقاف المؤقت والتعديل متاحين). PLAN_API_READ_ONLY: مفتاح API لحساب Website Archive، في كل طلب PATCH (بما في ذلك الإيقاف المؤقت والتعديل). PLAN_API_READ_ONLY مع details.reason \"session_outside_dashboard\": جلسة في لوحة التحكم لحساب Website Archive لا يحتوي طلبها على الترويسة Origin أو يحتوي عليها بعنوان ليس عنوان لوحة تحكم ProofSnap (يُنشأ العمل الجديد على هذه الخطة في لوحة التحكم فقط؛ وهذا قيد مرن وليس حدًا أمنيًا). PLAN_ARCHIVE_ONLY: استئناف جدول زمني على خطة Archive only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED: يستهدف url أو webhook_url عنوانًا خاصًا أو loopback أو link-local أو عنوانًا محظورًا بطريقة أخرى.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "عمليات الالتقاط المجدولة"
        ],
        "operationId": "deleteSchedule",
        "summary": "حذف جدول زمني",
        "description": "يوقف عمليات التشغيل المستقبلية. تُحفظ عمليات الالتقاط التي أُجريت بالفعل.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "تم الحذف.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schedule_id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "PLAN_API_READ_ONLY: مفتاح API لحساب Website Archive (مفتاحه للقراءة فقط؛ استخدم لوحة التحكم).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/scheduled-captures/{schedule_id}/runs": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ScheduleId"
        }
      ],
      "get": {
        "tags": [
          "عمليات الالتقاط المجدولة"
        ],
        "operationId": "listScheduleRuns",
        "summary": "عرض عمليات تشغيل جدول زمني",
        "description": "أحدث 50 عملية التقاط أجراها هذا الجدول (لا تُدرج فحوص on_change التي لم تجد تغييرًا).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "عمليات التشغيل.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schedule_id": {
                      "type": "string"
                    },
                    "runs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "capture_id": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "captured_at": {
                            "type": "string",
                            "nullable": true
                          },
                          "file_count": {
                            "type": "integer",
                            "nullable": true
                          }
                        }
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters": {
      "get": {
        "tags": [
          "القضايا"
        ],
        "operationId": "listMatters",
        "summary": "عرض القضايا",
        "description": "ما يصل إلى 200 قضية أنت عضو فيها. لا تُدرج قضايا شركة غادرتها.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "القضايا.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matters": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Matter"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: الخطة ليست من خطط Legal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "القضايا"
        ],
        "operationId": "createMatter",
        "summary": "إنشاء قضية",
        "description": "تصبح أنت مالكها. يبدأ سجل الحيازة بإدخال matter.created. تُزال المسافات من أطراف الحقول النصية وتُختصر إلى 200 حرف.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "نص عادي: تُدمج المسافات المتتالية وتُزال من الأطراف، بحد أقصى 200 حرف، دون أحرف تحكم أو أحرف اتجاه النص (bidi) أو أحرف غير مرئية (يُسمح بأداتي الوصل U+200C وU+200D اللتين تحتاجهما كتابة الهندية والفارسية) (وإلا 400 MALFORMED_REQUEST). حقل مطلوب. يظهر في صفحة التحقق العامة من القضية."
                  },
                  "client": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "نص عادي: تُدمج المسافات المتتالية وتُزال من الأطراف، بحد أقصى 200 حرف، دون أحرف تحكم أو أحرف اتجاه النص (bidi) أو أحرف غير مرئية (يُسمح بأداتي الوصل U+200C وU+200D اللتين تحتاجهما كتابة الهندية والفارسية) (وإلا 400 MALFORMED_REQUEST). القيمة الفارغة أو null تعني عدم التعيين (تُخزَّن null). تُرفض القيمة التي ليست سلسلة نصية.",
                    "nullable": true
                  },
                  "case_number": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "نص عادي: تُدمج المسافات المتتالية وتُزال من الأطراف، بحد أقصى 200 حرف، دون أحرف تحكم أو أحرف اتجاه النص (bidi) أو أحرف غير مرئية (يُسمح بأداتي الوصل U+200C وU+200D اللتين تحتاجهما كتابة الهندية والفارسية) (وإلا 400 MALFORMED_REQUEST). القيمة الفارغة أو null تعني عدم التعيين (تُخزَّن null). تُرفض القيمة التي ليست سلسلة نصية. يظهر في صفحة التحقق العامة من القضية.",
                    "nullable": true
                  },
                  "opposing_party": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "نص عادي: تُدمج المسافات المتتالية وتُزال من الأطراف، بحد أقصى 200 حرف، دون أحرف تحكم أو أحرف اتجاه النص (bidi) أو أحرف غير مرئية (يُسمح بأداتي الوصل U+200C وU+200D اللتين تحتاجهما كتابة الهندية والفارسية) (وإلا 400 MALFORMED_REQUEST). القيمة الفارغة أو null تعني عدم التعيين (تُخزَّن null). تُرفض القيمة التي ليست سلسلة نصية.",
                    "nullable": true
                  },
                  "jurisdiction": {
                    "type": "string",
                    "description": "نص حر. نص عادي: تُدمج المسافات المتتالية وتُزال من الأطراف، بحد أقصى 200 حرف، دون أحرف تحكم أو أحرف اتجاه النص (bidi) أو أحرف غير مرئية (يُسمح بأداتي الوصل U+200C وU+200D اللتين تحتاجهما كتابة الهندية والفارسية) (وإلا 400 MALFORMED_REQUEST). القيمة الفارغة أو null تعني عدم التعيين (تُخزَّن null). تُرفض القيمة التي ليست سلسلة نصية. يظهر في صفحة التحقق العامة من القضية.",
                    "maxLength": 200,
                    "nullable": true
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Smith v. Jones",
                "case_number": "2026-CV-0142"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "تم الإنشاء.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Matter"
                }
              }
            }
          },
          "400": {
            "description": "MISSING_FIELD: الحقل name مفقود أو فارغ. MALFORMED_REQUEST: JSON غير صالح. MALFORMED_REQUEST: حقل نصي ليس سلسلة نصية، أو أطول من 200 حرف، أو يحتوي على أحرف تحكم أو أحرف اتجاه النص.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: مستدعٍ في الوضع الفعلي تأخر سداد اشتراكه (أو اشتراك شركته) أو لم يُدفع."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: الخطة ليست من خطط Legal. EMAIL_NOT_VERIFIED: جلسة في لوحة التحكم لم يُؤكَّد عنوان بريدها الإلكتروني، أو مفتاح API لحساب كهذا.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "get": {
        "tags": [
          "القضايا"
        ],
        "operationId": "getMatter",
        "summary": "جلب قضية",
        "description": "القضية مع عمليات الالتقاط الخاصة بها (حتى 500)، وسجل الحيازة الخاص بها، وما يصل إلى 100 حدث وصول. يعطي `members` البريد الإلكتروني لكل إدخال في `member_uids`، ويربط `actor_emails` معرّفات uid المذكورة في سجل الحيازة وaudit_stream لهذه القضية (منفذي الإجراءات، وهدف `member.added` / `member.removed`، أي الأعضاء المُزالين أيضًا) ببريدهم الإلكتروني؛ ويكون البريد الإلكتروني null، أو يغيب uid عن الخريطة، عندما تتعذر قراءته. لا يتلقى هذه الحقول إلا المستدعون القادرون على فتح القضية؛ ولا يحمل `/matter-verify` أي بريد إلكتروني أبدًا. المستدعي الذي يحق له إلغاء رابط المشاركة العام (مالك القضية، أو مالك شركة منتهية أو مؤرشفة) يتلقى أيضًا `share`: `{ share_url, created_at, expires_at }` للرابط النشط (`expires_at` دائمًا null: تعمل الروابط حتى إلغائها)، أو null عند عدم وجود رابط؛ ولا يتلقى الأعضاء الآخرون هذا الحقل أبدًا. تحمل إدخالات audit_stream الحقلين source_ip وuser_agent لمالك القضية فقط؛ ويتلقى الأعضاء الإدخالات دون هذين الحقلين.\n\nقضية الشركة التي انتهت تحمل `read_only: true`.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "القضية.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Matter"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "members": {
                          "type": "array",
                          "description": "إدخال واحد لكل إدخال في `member_uids`، بالترتيب نفسه.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "uid": {
                                "type": "string"
                              },
                              "email": {
                                "type": "string",
                                "format": "email",
                                "nullable": true,
                                "description": "null عندما تتعذر قراءة الحساب."
                              }
                            }
                          }
                        },
                        "actor_emails": {
                          "type": "object",
                          "description": "`{ uid: email }` للأشخاص المذكورين في custody_log وaudit_stream لهذه القضية (باستثناء الفاعل system). يغيب uid الذي تتعذر قراءة بريده الإلكتروني.",
                          "additionalProperties": {
                            "type": "string",
                            "format": "email"
                          }
                        },
                        "captures": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/MatterCapture"
                          }
                        },
                        "custody_log": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CustodyEntry"
                          }
                        },
                        "audit_stream": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/AuditEntry"
                          }
                        },
                        "share": {
                          "type": "object",
                          "nullable": true,
                          "description": "فقط للمستدعي الذي يحق له إلغاء الرابط. null عند عدم وجود رابط نشط.",
                          "properties": {
                            "share_url": {
                              "type": "string",
                              "format": "uri"
                            },
                            "created_at": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true
                            },
                            "expires_at": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true,
                              "description": "دائمًا null: لا تنتهي صلاحية روابط المشاركة."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: الخطة ليست من خطط Legal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "القضايا"
        ],
        "operationId": "updateMatter",
        "summary": "تحديث قضية",
        "description": "للمالك فقط. تُزال المسافات من أطراف الحقول النصية وتُختصر إلى 200 حرف. الحقل name مطلوب: عند إرساله يجب أن يكون سلسلة نصية غير فارغة، والاسم الفارغ أو المكون من مسافات فقط أو null أو القيمة التي ليست سلسلة نصية يُرفض بالخطأ 400 ولا يتغير شيء. الأعضاء: يضيف add_member_email شخصًا لديه بالفعل حساب ProofSnap وينتمي إلى شركة القضية؛ وعدم وجود حساب، والعنوان غير المؤكد، والحساب من خارج الشركة تحصل كلها على الاستجابة نفسها 404 NOT_FOUND؛ ويزيل remove_member_uid عضوًا (وليس المالك). عند إرسال الاثنين، يُطبَّق add_member_email فقط. تضم القضية 50 عضوًا على الأكثر (القضية المكتملة العدد ترفض الطلب قبل البحث عن العنوان). تُكتب تغييرات الأعضاء في سجل الحيازة. يُكتب تغيير name أو client أو case_number أو opposing_party أو jurisdiction أو status في سجل الحيازة بوصفه matter.updated؛ وهدفه (target) سلسلة JSON تحتوي على fields (أسماء الحقول المتغيرة)، وعند تغيّر الحالة، على status الجديدة. لا تُكتب قيم النص الحر في سجل الحيازة العام الذي تُحسب له البصمات: تذهب القيم القديمة والجديدة إلى سجل الوصول (audit_stream، الإدخال matter.updated). في القضية المؤرشفة يُرفض تغيير الاسم أو add_member_email بالخطأ 409 MATTER_ARCHIVED ما لم يُعِد الطلب نفسه فتحها (status \"open\")؛ ويظل remove_member_uid وإعادة الفتح يعملان. تُحتسب كل دعوة ضمن حد يومي قدره 50 لكل مالك يرسل الدعوات (تُحتسب دعوات الشركة من لوحة التحكم وadd_member_email للقضايا معًا، بما في ذلك حالات الرفض، لأن كلًا منها يبحث عن عنوان)؛ وعند بلوغ الحد يعيد الطلب 429 RATE_LIMITED. لا يُحتسب الإخفاق من جهة الخادم.\n\nالحفظ القانوني: يضبط `{ \"legal_hold\": true|false }` وحده الحفظ القانوني للقضية (لمالك القضية فقط) ويطبقه على عمليات الالتقاط الخاصة بها في الطلب نفسه، بآثار الحفظ القانوني لعملية الالتقاط (الحفظ القانوني S3 Object Lock، وإدخالات الحيازة capture.legal_hold_placed / capture.legal_hold_released، وسلسلة أحداث عملية الالتقاط). الاستجابة هي القضية مع `changed` (تغيّرت العلامة)، و`captures_held` أو `captures_released`، و`skipped` ([{ capture_id, code }]، مثلًا عملية التقاط في سلة المهملات) و`truncated` (أكثر من 1000 عملية التقاط: أرسل القيمة نفسها مجددًا للمتابعة). عمليات الالتقاط التي تُسند إلى القضية لاحقًا أثناء سريان الحفظ تُحفظ عند إسنادها. وحدة واحدة من حد المعدّل لكل طلب، إضافة إلى وحدة لكل 10 عمليات التقاط تغيّرت.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "description": "يجب أن يحتوي على حرف واحد على الأقل غير المسافة. أغفله للإبقاء على الاسم الحالي. تنطبق قاعدة النص العادي نفسها: تُدمج المسافات المتتالية، بحد أقصى 200 حرف، دون أحرف تحكم أو أحرف اتجاه النص أو أحرف غير مرئية (يُسمح بـ U+200C وU+200D).",
                    "maxLength": 200
                  },
                  "client": {
                    "type": "string",
                    "nullable": true,
                    "description": "نص عادي: تُدمج المسافات المتتالية وتُزال من الأطراف، بحد أقصى 200 حرف، دون أحرف تحكم أو أحرف اتجاه النص (bidi) أو أحرف غير مرئية (يُسمح بأداتي الوصل U+200C وU+200D اللتين تحتاجهما كتابة الهندية والفارسية) (وإلا 400 MALFORMED_REQUEST). القيمة null أو السلسلة المكونة من مسافات فقط تمسح الحقل (تُخزَّن null). تُرفض القيمة التي ليست سلسلة نصية بالخطأ 400.",
                    "maxLength": 200
                  },
                  "case_number": {
                    "type": "string",
                    "nullable": true,
                    "description": "نص عادي: تُدمج المسافات المتتالية وتُزال من الأطراف، بحد أقصى 200 حرف، دون أحرف تحكم أو أحرف اتجاه النص (bidi) أو أحرف غير مرئية (يُسمح بأداتي الوصل U+200C وU+200D اللتين تحتاجهما كتابة الهندية والفارسية) (وإلا 400 MALFORMED_REQUEST). القيمة null أو السلسلة المكونة من مسافات فقط تمسح الحقل (تُخزَّن null). تُرفض القيمة التي ليست سلسلة نصية بالخطأ 400.",
                    "maxLength": 200
                  },
                  "opposing_party": {
                    "type": "string",
                    "nullable": true,
                    "description": "نص عادي: تُدمج المسافات المتتالية وتُزال من الأطراف، بحد أقصى 200 حرف، دون أحرف تحكم أو أحرف اتجاه النص (bidi) أو أحرف غير مرئية (يُسمح بأداتي الوصل U+200C وU+200D اللتين تحتاجهما كتابة الهندية والفارسية) (وإلا 400 MALFORMED_REQUEST). القيمة null أو السلسلة المكونة من مسافات فقط تمسح الحقل (تُخزَّن null). تُرفض القيمة التي ليست سلسلة نصية بالخطأ 400.",
                    "maxLength": 200
                  },
                  "jurisdiction": {
                    "type": "string",
                    "nullable": true,
                    "description": "نص عادي: تُدمج المسافات المتتالية وتُزال من الأطراف، بحد أقصى 200 حرف، دون أحرف تحكم أو أحرف اتجاه النص (bidi) أو أحرف غير مرئية (يُسمح بأداتي الوصل U+200C وU+200D اللتين تحتاجهما كتابة الهندية والفارسية) (وإلا 400 MALFORMED_REQUEST). القيمة null أو السلسلة المكونة من مسافات فقط تمسح الحقل (تُخزَّن null). تُرفض القيمة التي ليست سلسلة نصية بالخطأ 400.",
                    "maxLength": 200
                  },
                  "status": {
                    "type": "string",
                    "description": "لختم القضية استخدم POST /matters/{matter_id}/seal.",
                    "enum": [
                      "open",
                      "archived"
                    ]
                  },
                  "add_member_email": {
                    "type": "string",
                    "format": "email"
                  },
                  "remove_member_uid": {
                    "type": "string"
                  },
                  "legal_hold": {
                    "type": "boolean",
                    "description": "الحفظ القانوني للقضية، يُرسل وحده (مع حقل آخر: 400). القيمة true تحفظ كل عملية التقاط حالية في القضية وكل عملية التقاط تُسند إليها أثناء سريان الحفظ (legal_hold_source \"matter:<id>\"؛ وعملية الالتقاط المحفوظة بالفعل تحتفظ بحفظها الخاص). القيمة false ترفع فقط الحفظ الذي وضعته القضية؛ ويبقى الحفظ اليدوي أو الجماعي. لمالك القضية فقط. لا يمنعه تأخر سداد الاشتراك. يتطلب وضع الحفظ خطة مدفوعة (403 LEGAL_HOLD_NOT_AVAILABLE، details.reason plan) وتُتخطى عمليات الالتقاط في وضع الاختبار (الرمز LEGAL_HOLD_NOT_AVAILABLE في `skipped`)؛ وتُقبل false وحدها أيضًا بعد انتهاء خطة Legal."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "القضية المحدّثة.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Matter"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: الحقل name فارغ أو مكون من مسافات فقط أو ليس سلسلة نصية؛ أو حقل نصي ليس سلسلة نصية أو أطول من 200 حرف أو يحتوي على أحرف تحكم أو أحرف اتجاه النص؛ أو قيمة status غير صالحة؛ أو لا يوجد ما يُحدَّث؛ أو الشخص عضو بالفعل؛ أو محاولة إزالة المالك.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: مستدعٍ في الوضع الفعلي تأخر سداد اشتراكه (أو اشتراك شركته) أو لم يُدفع."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED، أو FORBIDDEN (لست المالك)، أو LEGAL_HOLD_NOT_AVAILABLE (legal_hold true دون خطة مدفوعة)، أو EMAIL_NOT_VERIFIED: جلسة في لوحة التحكم لم يُؤكَّد عنوان بريدها الإلكتروني، أو مفتاح API لحساب كهذا.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND، أو NOT_FOUND (لا يوجد حساب مؤهل لهذا البريد الإلكتروني: لا حساب، أو غير مؤكد، أو من خارج الشركة، باستجابة واحدة للجميع؛ أو remove_member_uid ليس عضوًا).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED، أو MATTER_ARCHIVED (تغيير الاسم أو إضافة عضو جديد إلى قضية مؤرشفة). COMPANY_READ_ONLY: تنتمي القضية إلى شركة انتهى اشتراكها أو تُحفظ كأرشيف شركة (المستدعي على خطة Legal؛ ويظل الفتح والتصدير وإلغاء رابط المشاركة متاحة).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: بُلغ الحد اليومي البالغ 50 دعوة أعضاء لكل مالك يرسل الدعوات (تُحتسب دعوات الشركة من لوحة التحكم وadd_member_email للقضايا معًا؛ details.daily_limit، details.action \"member_invite\"). تحدد Retry-After موعد انتهاء اليوم وفق UTC.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}/captures": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "القضايا"
        ],
        "operationId": "assignMatterCaptures",
        "summary": "إضافة عمليات التقاط أو إزالتها",
        "description": "يمكن لأي عضو استدعاء هذا، لكن لا تُنقل إلا عمليات الالتقاط التي أنشأها المستدعي؛ وتُعاد الأخرى في skipped، وكذلك عمليات الالتقاط الموجودة بالفعل في قضية أخرى وعمليات الالتقاط الموجودة بالفعل في هذه القضية (أو، مع unassign، غير الموجودة فيها). تُتجاهل الإدخالات التي ليست معرّفات عمليات التقاط، ويُعالج 100 إدخال على الأكثر. يُكتب كل تغيير في سجل الحيازة. لا تقبل القضية المؤرشفة عمليات التقاط جديدة (409 MATTER_ARCHIVED)؛ ويظل unassign يعمل.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "capture_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "maxItems": 100
                  },
                  "unassign": {
                    "type": "boolean",
                    "description": "القيمة true تزيل عمليات الالتقاط من القضية.",
                    "default": false
                  }
                },
                "required": [
                  "capture_ids"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "النتيجة.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "assigned": {
                      "type": "integer",
                      "description": "عدد عمليات الالتقاط التي تغيّرت."
                    },
                    "skipped": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "unassign": {
                      "type": "boolean"
                    },
                    "legal_holds_placed": {
                      "type": "integer",
                      "description": "يظهر عندما يكون الحفظ القانوني للقضية مفعّلًا: عدد عمليات الالتقاط المُسندة التي وضعها تحت الحفظ."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: لا يوجد أي معرّف صالح لعملية التقاط.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: مستدعٍ في الوضع الفعلي تأخر سداد اشتراكه (أو اشتراك شركته) أو لم يُدفع."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED، أو EMAIL_NOT_VERIFIED: جلسة في لوحة التحكم لم يُؤكَّد عنوان بريدها الإلكتروني، أو مفتاح API لحساب كهذا.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED، أو MATTER_ARCHIVED (الإضافة إلى قضية مؤرشفة). COMPANY_READ_ONLY: تنتمي القضية إلى شركة انتهى اشتراكها أو تُحفظ كأرشيف شركة (المستدعي على خطة Legal؛ يظل الفتح والتصدير وإلغاء رابط المشاركة متاحة).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}/seal": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "القضايا"
        ],
        "operationId": "sealMatter",
        "summary": "ختم قضية",
        "description": "للمالك فقط. يثبّت البصمة الحالية لرأس سلسلة الحيازة عبر OpenTimestamps، وكذلك بطابع زمني إلكتروني مؤهل وفق eIDAS عندما يكون eIDAS مهيّأً في النشر ويكون المستدعي في الوضع الفعلي (مفتاح API فعلي، أو جلسة في لوحة التحكم مرتبطة بالفوترة). لا يُفوتَر الطابع الزمني وفق eIDAS الخاص بالختم، وإذا فشل يستمر الختم بدونه. المستدعي في وضع الاختبار (مفتاح اختبار أو جلسة في وضع الاختبار (sandbox)) تُختم قضيته باستخدام OpenTimestamps فقط، ويسجّل الختم eidas_status بالقيمة not_obtained مع eidas_reason بالقيمة test_key؛ لا يقبل الطلب أي خيار خاص بـ eIDAS، لذا فهذا ليس خطأ. بعد ذلك تصبح القضية للقراءة فقط: لا تعديلات، ولا تغييرات على عمليات الالتقاط، ولا يمكن حذف عمليات الالتقاط التابعة لها. لا تُختم قضية لا تزال تضم عملية التقاط في سلة المهملات (409 MATTER_HAS_TRASHED_CAPTURES): استعد عملية الالتقاط أو أزلها من القضية أولًا.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "تم الختم.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "sealed"
                      ]
                    },
                    "seal": {
                      "$ref": "#/components/schemas/MatterSeal"
                    },
                    "custody_head_hash": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: لا يوجد للقضية رأس لسلسلة الحيازة.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: مستدعٍ في الوضع الفعلي تأخر سداد اشتراكه (أو اشتراك شركته) أو لم يُدفع."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: الخطة ليست من خطط Legal. FORBIDDEN: لست المالك. EMAIL_NOT_VERIFIED: جلسة في لوحة التحكم لم يُؤكَّد عنوان بريدها الإلكتروني، أو مفتاح API لحساب كهذا.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED (مختومة بالفعل)، أو MATTER_CHANGED (تغيّر سجل الحيازة أثناء الختم؛ أعد المحاولة)، أو MATTER_HAS_TRASHED_CAPTURES (إحدى عمليات الالتقاط في القضية موجودة في سلة المهملات؛ استعدها أو أزلها من القضية أولًا). COMPANY_READ_ONLY: تنتمي القضية إلى شركة انتهى اشتراكها أو تُحفظ كأرشيف شركة (المستدعي على خطة Legal؛ يظل الفتح والتصدير وإلغاء رابط المشاركة متاحة).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "TIMESTAMP_SERVICE_UNAVAILABLE (فشل التثبيت ولم يتغيّر شيء؛ أعد المحاولة) أو CONFIGURATION_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "EIDAS_SEAL_RETRY: لم يصل الطابع الزمني وفق eIDAS الخاص بالختم في الوقت المحدد (أو لا يزال قيد الإصدار). لم يُختم أي شيء. أعد المحاولة بعد عدد الثواني المحدد في Retry-After: تتلقى المحاولة التالية الطابع الزمني الذي صدر بالفعل لرأس سلسلة الحيازة هذا، دون طابع ثانٍ. SERVICE_UNAVAILABLE: تعذّر على البوابة التحقق من بيانات الاعتماد؛ أعد المحاولة مع زيادة فترة الانتظار تدريجيًا.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "EIDAS_SEAL_RETRY",
                    "message": "The eIDAS timestamp of the seal did not arrive in time. Nothing was sealed. Retry after the Retry-After interval; the retry reuses the timestamp already issued for this custody head.",
                    "details": {
                      "reason": "The eIDAS timestamp did not arrive in time. Nothing was sealed. Retry the seal.",
                      "retry_after_seconds": 15
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          }
        }
      }
    },
    "/matters/{matter_id}/export": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "القضايا"
        ],
        "operationId": "exportMatter",
        "summary": "تصدير قضية",
        "description": "للمالك فقط. ينشئ ملف ZIP واحدًا يضم custody_report.pdf وcustody_log.json وmatter.json وREADME.txt وaccess_log.json (عند وجود أحداث وصول) وحزمة الأدلة لكل عملية التقاط. عند تفعيل ترقيم Bates (الافتراضي) يضيف نسخًا من كل evidence.pdf تحمل ترقيم Bates ضمن exhibits/ (تُرقَّم المستندات المرفقة بدءًا من أقدم دليل)، ومع formats.loadfile يضيف ملف استيراد (load file) لبرنامجَي Concordance وOpticon. ومع formats.pdfa تُحوَّل نسخ الإيداع أيضًا إلى PDF/A عندما يكون المحوّل متاحًا (في التصدير المتزامن ضمن ميزانية زمنية فقط)؛ النسخ المحتسبة في pdfa_count وحدها بصيغة PDF/A، أما البقية فملفات PDF لا تدّعي التوافق مع PDF/A، ولا يذكر README.txt صيغة PDF/A إلا عندما تكون قيمة pdfa_count أكبر من 0.\n\nتُصدَّر القضية التي تضم ما يصل إلى 20 عملية التقاط بشكل متزامن (200). أما القضية الأكبر فتُصدَّر في الخلفية: تكون الاستجابة 202 مع job_id؛ استعلم دوريًا عبر GET /matters/{matter_id}/export?job_id=. عمليات الالتقاط التي تتجاوز حد الحجم (120 MB في التصدير المتزامن؛ 800 MB و300 عملية التقاط في الخلفية) تُدرج في التقارير لكنها لا تُضمَّن في الحزمة.\n\nيعمل على القضايا المفتوحة والمختومة. تُرفض القضية المؤرشفة بالرمز 409 MATTER_ARCHIVED (أعد فتحها أولًا)، باستثناء المستدعي الذي لا يستطيع إعادة فتحها: مالك شركة منتهية خلال مدة الاحتفاظ بها، وحساب Archive only. يُسجَّل كل تصدير في سجل الوصول بوصفه export.generated. يُحتسب كل تصدير ضمن حد يومي قدره 50 لكل مستخدم؛ والتصدير في الخلفية الذي يفشل يعيد وحدته.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "bates": {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean",
                        "default": true
                      },
                      "prefix": {
                        "type": "string",
                        "description": "أحرف وأرقام فقط، بأحرف كبيرة، حتى 12 حرفًا. الافتراضي: مأخوذ من رقم القضية أو اسم القضية."
                      },
                      "start": {
                        "type": "integer",
                        "minimum": 1,
                        "default": 1
                      },
                      "exhibit_start": {
                        "type": "integer",
                        "minimum": 1,
                        "default": 1
                      },
                      "pad": {
                        "type": "integer",
                        "minimum": 4,
                        "maximum": 8,
                        "default": 6
                      }
                    }
                  },
                  "formats": {
                    "type": "object",
                    "properties": {
                      "loadfile": {
                        "type": "boolean",
                        "description": "تضمين ملف الاستيراد (load file) الخاص بالاكتشاف الإلكتروني (e-discovery) (يتطلب ترقيم Bates).",
                        "default": true
                      },
                      "pdfa": {
                        "type": "boolean",
                        "description": "تحويل نسخ الإيداع التي تحمل ترقيم Bates إلى PDF/A (يتطلب ترقيم Bates). بأفضل جهد: النسخة التي لم تُحوَّل تبقى ملف PDF لا يدّعي التوافق مع PDF/A؛ ويبيّن pdfa_count في الاستجابة عدد النسخ المحوّلة.",
                        "default": true
                      }
                    }
                  },
                  "export_locale": {
                    "type": "string",
                    "description": "لغة custody_report.pdf وفهرس المستندات المرفقة وREADME.txt: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk (يُقبل الواصل بدلًا من الشرطة السفلية). القيم الأخرى تعود إلى الإنجليزية."
                  },
                  "declaration": {
                    "type": "object",
                    "description": "مقبول للتوافق. تخضع حقوله النصية (name, title, party, case_caption, executed_location) لقاعدة النص العادي (انظر PlainText): تُرفض القيمة التي تحتوي على حرف محظور بالرمز 400 MALFORMED_REQUEST. تُختصر القيم الطويلة جدًا (name وtitle وexecuted_location إلى 120، وparty إلى 160، وcase_caption إلى 200 حرف)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "التصدير جاهز.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "description": "رابط تنزيل موقَّع مسبقًا، صالح لمدة 24 ساعة.",
                      "format": "uri"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "capture_count": {
                      "type": "integer"
                    },
                    "captures_included": {
                      "type": "integer"
                    },
                    "captures_skipped": {
                      "type": "integer",
                      "description": "عمليات الالتقاط المدرجة في التقارير لكنها لم تُضمَّن في الحزمة بسبب بلوغ حد الحجم."
                    },
                    "custody_events": {
                      "type": "integer"
                    },
                    "exhibits_count": {
                      "type": "integer",
                      "description": "نسخ المستندات المرفقة التي تحمل ترقيم Bates."
                    },
                    "bates_prefix": {
                      "type": "string",
                      "nullable": true
                    },
                    "searchable": {
                      "type": "boolean",
                      "description": "دائمًا false: لا تُضاف طبقة نصية إلى نسخ الإيداع. يُحتفظ به للحفاظ على بنية استجابة ثابتة."
                    },
                    "pdfa_count": {
                      "type": "integer",
                      "description": "نسخ الإيداع ضمن exhibits/ التي بصيغة PDF/A. القيمة 0 تعني أنه لا توجد نسخة بصيغة PDF/A ولا نسخة تدّعي ذلك."
                    },
                    "loadfile": {
                      "type": "boolean"
                    },
                    "size_bytes": {
                      "type": "integer"
                    },
                    "generated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "بدأ التصدير في الخلفية.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "async": {
                      "type": "boolean"
                    },
                    "job_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "poll_path": {
                      "type": "string",
                      "description": "يتضمن المرحلة /v1."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: أحد خيارات التصدير غير صالح (details.reason).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: مستدعٍ في الوضع الفعلي تأخر سداد اشتراكه (أو اشتراك شركته) أو لم يُدفع."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: الخطة ليست من خطط Legal. FORBIDDEN: لست المالك. EMAIL_NOT_VERIFIED: جلسة في لوحة التحكم لم يُؤكَّد عنوان بريدها الإلكتروني، أو مفتاح API لحساب كهذا.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_ARCHIVED: أعد فتح القضية أولًا (PATCH مع status \"open\").",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "CONFIGURATION_ERROR: تعذّر إنشاء التصدير أو بدؤه.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          },
          "504": {
            "description": "UPSTREAM_TIMEOUT: لم يكتمل التصدير المتزامن خلال 25 ثانية تقريبًا (details.reason). أعد المحاولة.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "القضايا"
        ],
        "operationId": "getMatterExport",
        "summary": "الاستعلام عن حالة تصدير في الخلفية",
        "description": "للمالك فقط. المهمة التي لا تزال قيد المعالجة بعد 30 دقيقة من بدئها يُبلَّغ عنها بالحالة failed مع الخطأ \"export timed out\". تنتهي المهمة مرة واحدة: المهمة التي أُبلغ عن فشلها لا تصبح ready لاحقًا أبدًا، والمهمة الجاهزة لا يُبلَّغ عنها بالحالة failed أبدًا.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "حالة المهمة. عندما تكون جاهزة، يُملأ url والعدّادات.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing",
                        "ready",
                        "failed"
                      ]
                    },
                    "url": {
                      "type": "string",
                      "nullable": true
                    },
                    "captures_included": {
                      "type": "integer",
                      "nullable": true
                    },
                    "captures_skipped": {
                      "type": "integer",
                      "nullable": true
                    },
                    "custody_events": {
                      "type": "integer",
                      "nullable": true
                    },
                    "size_bytes": {
                      "type": "integer",
                      "nullable": true
                    },
                    "error": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: job_id مفقود.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: الخطة ليست من خطط Legal. FORBIDDEN: لست المالك.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND، أو NOT_FOUND لمهمة غير معروفة.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}/share": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "القضايا"
        ],
        "operationId": "shareMatter",
        "summary": "إنشاء رابط تحقق عام",
        "description": "للمالك فقط. يعيد رابطًا يستطيع أي شخص فتحه لرؤية اسم القضية ورقمها والاختصاص القضائي والحالة وسجل الحيازة والختم (انظر GET /matter-verify). لا يُشارَك أي محتوى من عمليات الالتقاط. استدعاؤه مرة أخرى يعيد الرابط نفسه. يظل الرابط صالحًا إلى أن تلغيه.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "رابط المشاركة.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "share_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "token": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: مستدعٍ في الوضع الفعلي تأخر سداد اشتراكه (أو اشتراك شركته) أو لم يُدفع."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: الخطة ليست من خطط Legal. FORBIDDEN: لست المالك. EMAIL_NOT_VERIFIED: جلسة في لوحة التحكم لم يُؤكَّد عنوان بريدها الإلكتروني، أو مفتاح API لحساب كهذا.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_ARCHIVED: لا تُشارَك القضية المؤرشفة. COMPANY_READ_ONLY: تنتمي القضية إلى شركة انتهى اشتراكها أو تُحفظ كأرشيف شركة (المستدعي على خطة Legal؛ يظل الفتح والتصدير وإلغاء رابط المشاركة متاحة).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "القضايا"
        ],
        "operationId": "revokeMatterShare",
        "summary": "إلغاء الرابط العام",
        "description": "للمالك فقط. يتوقف الرابط عن العمل. ينشئ طلب POST لاحق رابطًا جديدًا. يستطيع مالك شركة منتهية (أو شركة محفوظة كأرشيف شركة) ومالك قضية على خطة Archive only إلغاء الرابط دون خطة Legal (يظل إنشاء الرابط ميزة من ميزات خطة Legal).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "تم الإلغاء.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "revoked": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: مستدعٍ في الوضع الفعلي تأخر سداد اشتراكه (أو اشتراك شركته) أو لم يُدفع."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED، أو FORBIDDEN (لست المالك)، أو EMAIL_NOT_VERIFIED: جلسة في لوحة التحكم لم يُؤكَّد عنوان بريدها الإلكتروني، أو مفتاح API لحساب كهذا.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "عام"
        ],
        "operationId": "health",
        "summary": "فحص سلامة الخدمة",
        "description": "يعيد دائمًا 200 ما دامت API تستجيب. مع deep=1 (أو true) تضيف الاستجابة dependencies: هل كل خدمة تابعة مهيّأة، ولـ eIDAS فحص فعلي لخدمة الطوابع الزمنية. يُفحص eIDAS وحده؛ أما البقية فتُبلغ عن التهيئة فقط. تصبح قيمة status هي degraded عندما يكون eIDAS مهيّأً ويفشل فحصه. الفحص الذي لا يجيب في الوقت المحدد يُبلَّغ عنه في dependencies (probe_timeout، أو eidas.error بالقيمة timeout)، ولا يظهر أبدًا كفشل لـ /health. يخضع deep=1 لحد المعدّل لكل عنوان IP للعميل (10 في الدقيقة): عند تجاوزه يعيد الطلب 429 RATE_LIMITED مع Retry-After.",
        "security": [],
        "parameters": [
          {
            "name": "deep",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "حالة السلامة.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "degraded"
                      ]
                    },
                    "stage": {
                      "type": "string"
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "uptime_ms": {
                      "type": "integer",
                      "description": "عدد الملّي ثانية منذ بدء تشغيل المثيل الذي يخدم الطلب."
                    },
                    "version": {
                      "type": "string"
                    },
                    "dependencies": {
                      "type": "object",
                      "properties": {
                        "eidas": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            },
                            "healthy": {
                              "type": "boolean",
                              "description": "null عندما لا يكون مهيّأً.",
                              "nullable": true
                            },
                            "error": {
                              "type": "string",
                              "description": "رمز ثابت عندما لا يُبلغ فحص eIDAS عن سلامة الخدمة: timeout (لا إجابة خلال 15 ثانية)، unavailable (أجابت خدمة الطوابع الزمنية بأنها غير سليمة)، error (تعذّر تشغيل الفحص). تُسجَّل التفاصيل على الخادم ولا تُعاد أبدًا.",
                              "enum": [
                                "timeout",
                                "unavailable",
                                "error"
                              ]
                            }
                          }
                        },
                        "ots": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "c2pa_sign": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "ntp": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "whois": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "error": {
                          "type": "string",
                          "description": "فقط عندما يفشل فحص التبعيات نفسه: probe_timeout إذا لم يُجب خلال 20 ثانية، وprobe_failed في غير ذلك. تظل الاستجابة 200."
                        }
                      },
                      "description": "فقط مع deep=1."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/ledger/{hash}": {
      "get": {
        "tags": [
          "عام"
        ],
        "operationId": "ledgerLookup",
        "summary": "البحث عن بصمة بيان",
        "description": "يتحقق مما إذا كان ProofSnap قد سجّل إصدار حزمة يحمل ملف manifest.json فيها بصمة SHA-256 هذه. تُسجَّل عمليات الالتقاط والشهادات والمقارنات والنسخ المنقّحة عند إصدارها، مع بصمة البيان الذي يُسلَّم معها (بعد خطوة الطابع الزمني المؤهل)؛ والحزم التي صدرت قبل وجود السجل العام غير موجودة فيه. القيمة issued: false تعني فقط أنه لا يوجد سجل.",
        "security": [],
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "بصمة SHA-256 لملف manifest.json، مكوّنة من 64 حرفًا ست عشريًا بأي حالة أحرف، ويمكن أن تسبقها البادئة sha256:.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "نتيجة البحث.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "manifest_sha256": {
                      "type": "string"
                    },
                    "issued": {
                      "type": "boolean"
                    },
                    "issued_at": {
                      "type": "string",
                      "nullable": true,
                      "format": "date-time",
                      "description": "غير موجود عندما تكون قيمة issued هي false."
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "capture",
                        "certify",
                        "compare",
                        "redaction"
                      ],
                      "nullable": true,
                      "description": "غير موجود عندما تكون قيمة issued هي false."
                    },
                    "eidas": {
                      "type": "boolean",
                      "nullable": true,
                      "description": "هل تحمل الحزمة الصادرة طابعًا زمنيًا إلكترونيًا مؤهلًا وفق eIDAS (manifest.json.tsr). غير موجود عندما تكون قيمة issued هي false؛ وnull للسجلات المكتوبة قبل 2026-10-01."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: البصمة ليست 64 حرفًا ست عشريًا.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/matter-verify": {
      "get": {
        "tags": [
          "عام"
        ],
        "operationId": "matterVerify",
        "summary": "العرض العام لقضية مُشارَكة",
        "description": "بيانات للقراءة فقط خلف رابط مشاركة القضية: تفاصيل القضية، وسجل الحيازة (بحيث يمكن إعادة حساب سلسلة البصمات)، والختم مع إثبات OpenTimestamps ورمز eIDAS. لا يتضمن أي محتوى من عمليات الالتقاط ولا روابط تنزيل ولا سجل الوصول. attested_by.organization هو اسم الشركة التي تنتمي إليها القضية كما حدّده المالك (قضية شركة)، أو null؛ والقضية الشخصية لا تذكر أي مؤسسة. custody_log[].actor_org هي الشركة التي كان ينتمي إليها المنفّذ عند كتابة ذلك الإدخال (تُسجَّل مع الإدخال، خارج البيانات التي تُحسب بصمتها)، وذلك في قضايا الشركات فقط؛ والإدخالات المكتوبة قبل بدء تسجيل ذلك تُظهر null. يخضع لحد المعدّل لكل عنوان IP للعميل (30 طلبًا كل 60 ثانية افتراضيًا).",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "عرض القضية.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "case_number": {
                          "type": "string",
                          "nullable": true
                        },
                        "jurisdiction": {
                          "type": "string",
                          "nullable": true
                        },
                        "status": {
                          "type": "string"
                        },
                        "sealed": {
                          "type": "boolean"
                        },
                        "attested_by": {
                          "type": "object",
                          "properties": {
                            "organization": {
                              "type": "string",
                              "nullable": true
                            }
                          }
                        },
                        "capture_count": {
                          "type": "integer"
                        },
                        "custody_count": {
                          "type": "integer"
                        },
                        "custody_head_hash": {
                          "type": "string",
                          "nullable": true
                        },
                        "seal": {
                          "type": "object",
                          "properties": {
                            "root_hash": {
                              "type": "string"
                            },
                            "ots_present": {
                              "type": "boolean"
                            },
                            "eidas_present": {
                              "type": "boolean"
                            },
                            "ots_hex": {
                              "type": "string",
                              "nullable": true
                            },
                            "ots_pending": {
                              "type": "boolean"
                            },
                            "tsr_base64": {
                              "type": "string",
                              "nullable": true
                            },
                            "eidas_serial": {
                              "type": "string",
                              "nullable": true
                            },
                            "eidas_issued_at": {
                              "type": "string",
                              "nullable": true
                            },
                            "eidas_ltv": {
                              "type": "boolean"
                            },
                            "sealed_at": {
                              "type": "string"
                            },
                            "eidas_status": {
                              "type": "string",
                              "enum": [
                                "confirmed",
                                "not_obtained",
                                "not_configured"
                              ],
                              "nullable": true,
                              "description": "null للأختام التي أُجريت قبل 2026-10-01 دون رمز eIDAS."
                            },
                            "eidas_reason": {
                              "type": "string",
                              "enum": [
                                "token_refused",
                                "unavailable",
                                "test_key",
                                "account_limit"
                              ],
                              "description": "فقط عندما تكون قيمة eidas_status هي not_obtained. test_key: أجرى الختمَ مستدعٍ في وضع الاختبار، لذا لم يُطلب طابع زمني وفق eIDAS. account_limit: ليس لدى الحساب فاتورة مدفوعة بعد وقد بلغ حد eIDAS الخاص به (20 في اليوم بتوقيت UTC، و50 في الشهر بتوقيت UTC)، لذا لم يُطلب طابع زمني وفق eIDAS."
                            },
                            "eidas_verification_status": {
                              "type": "string",
                              "nullable": true,
                              "description": "verified عندما اجتاز الرمز فحص ProofSnap عند الختم."
                            },
                            "eidas_revocation_status": {
                              "type": "string",
                              "nullable": true,
                              "enum": [
                                "good",
                                "revoked_after_gen_time",
                                "unknown",
                                "not_checked",
                                null
                              ],
                              "description": "حالة شهادة سلطة الطوابع الزمنية (TSA) المحدَّدة عند الختم (لا تُجلب بشكل مباشر)."
                            },
                            "eidas_validation": {
                              "type": "object",
                              "nullable": true,
                              "additionalProperties": true,
                              "description": "محتوى eidas_validation.json المسجَّل عند الختم (سلسلة شهادات TSA، والشهادة الجذرية المثبّتة، واستجابة OCSP، وCRL، وسجل التحقق) بحيث يمكن فحص الرمز الموجود في tsr_base64 دون اتصال. null في الأختام الأقدم."
                            }
                          },
                          "nullable": true
                        },
                        "created_at": {
                          "type": "string"
                        }
                      }
                    },
                    "custody_log": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/CustodyEntry"
                          },
                          {
                            "type": "object",
                            "properties": {
                              "actor_org": {
                                "type": "string",
                                "nullable": true
                              }
                            }
                          }
                        ]
                      }
                    },
                    "issuer": {
                      "type": "string",
                      "enum": [
                        "ProofSnap"
                      ]
                    },
                    "verified_at": {
                      "type": "string",
                      "nullable": true,
                      "description": "دائمًا null."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: رمز غير صالح أو غير معروف أو مُلغى.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/captures/export-jobs": {
      "post": {
        "tags": [
          "عمليات الالتقاط"
        ],
        "operationId": "startCaptureExportJob",
        "summary": "تصدير جميع عمليات الالتقاط كتنزيل واحد (مهمة غير متزامنة)",
        "description": "يبدأ مهمة تجمع كل عمليات الالتقاط الخاصة بك في تنزيل واحد، مقسَّم إلى أجزاء لا يتجاوز حجم كل منها 2 GB تقريبًا. كل جزء ملف ZIP عادي يضم: README.txt (بالإنجليزية) إضافة إلى README.<language>.txt (النص نفسه مترجمًا، عندما لا تكون language هي en؛ وكلاهما بترميز UTF-8 مع علامة ترتيب البايتات ونهايات أسطر CRLF؛ ويشير السطر 1 في كل منهما إلى الآخر)، وcaptures.csv (بترميز UTF-8 مع علامة ترتيب البايتات، وCRLF؛ ويضم كل عملية التقاط في التصدير بالأعمدة member (البريد الإلكتروني للمُنشئ في تصدير الشركة، وفارغ في تصديرك الخاص)، وcapture_id، وcreated_at (وقت استلام الطلب)، وcaptured_at (لحظة الالتقاط؛ فارغ لعملية التقاط فاشلة)، وurl، وstatus (completed أو failed أو unavailable)، وexport_part، وfile، وots_file (captures/<capture_id>.upgraded.ots عندما يحتوي ذلك الجزء على الإثبات، وإلا فارغ)، وtest_mode (true لعملية التقاط أُجريت بمفتاح اختبار: تحمل علامة مائية وليست دليلًا)، وnote (سبب عدم تضمين حزمة، وكذلك لكل صف unavailable)؛ والقائمة نفسها في كل جزء، وعندما تختلف الأجزاء تكون قائمة الجزء ذي الرقم الأعلى هي النهائية)، وcaptures/<capture_id>.zip، وهي حزمة الأدلة المختومة الأصلية بايتًا ببايت، إضافة إلى captures/<capture_id>.upgraded.ots عندما يكون إثبات OpenTimestamps المُحدَّث مخزّنًا. يُتحقق من كل حزمة داخلية بمفردها باستخدام verification/verify.sh أو verify.ps1 الخاص بها. لا تُضمَّن الحزمة التي يتجاوز حجمها 2 GB (تُدرج بالحالة unavailable مع ملاحظة؛ نزّلها بمفردها عبر GET /captures/{capture_id}). تُدرج عمليات الالتقاط الفاشلة في captures.csv فقط؛ ولا تُصدَّر عمليات الالتقاط الموجودة في سلة المهملات أو المحذوفة نهائيًا. تُؤرَّخ إدخالات كل جزء بوقت التصدير (UTC، وكذلك في حقل الطابع الزمني الموسّع). استعلم دوريًا عبر GET /captures/export-jobs/{job_id} حتى تصبح قيمة status هي ready. **إعادة الاستخدام:** عندما يكون آخر تصدير لك جاهزًا، وله النطاق نفسه (وorg نفسه) واللغة نفسها، ويتبقى من نافذة تنزيله يوم واحد على الأقل، ولم تتغيّر صلاحياتك، ولم تُضَف أي عملية التقاط أو تكتمل أو تُنقل إلى سلة المهملات أو تُستعد أو تُحذف نهائيًا منذ التخطيط له، يعيد الطلب 200 مع تلك المهمة (reused: true): لا يُعاد بناء أي شيء ولا تُستهلك أي وحدة يومية. التصدير الجاهز الأحدث للنطاق نفسه يحل محل السابق (تصبح المهمة السابقة expired، مع expired_reason بالقيمة superseded). من يمكنه بدؤه: مفتاح API فعلي أو جلسة لوحة التحكم، على كل الخطط بما فيها وضع الاختبار (sandbox) وArchive only والمفاتيح المخصصة للقراءة فقط (Website Archive). يحصل مفتاح الاختبار على 403 FORBIDDEN (لأن التصدير يضم عمليات التقاط فعلية)، ويعيد GET /captures/export-jobs/{job_id} الرمز 404 EXPORT_JOB_NOT_FOUND لمفتاح الاختبار مع كل مهمة. يضم تصديرك الخاص (scope own) عمليات الالتقاط الشخصية الخاصة بك وتلك التي أجريتها لشركتك الحالية أو لشركة تملكها، وكذلك عبر مفتاح ما دامت تلك الشركة أرشيف شركة قائمًا (أما عمليات الالتقاط الخاصة بالمقاعد الأخرى في الأرشيف فلا يُوصل إليها إلا عبر جلسة لوحة التحكم). أما scope company (لمالك الشركة فقط، وكذلك لشركة منتهية خلال نافذة التنزيل الخاصة بها؛ ولأرشيف شركة قائم عبر جلسة لوحة التحكم فقط) فيصدّر عمليات الالتقاط التي أجراها كل مقعد للشركة إضافة إلى عملياتك الخاصة، مع البريد الإلكتروني للمُنشئ في العمود member. مهمة واحدة قيد التشغيل لكل حساب (يذكرها الرمز 409 EXPORT_JOB_RUNNING)، و5 مهام مبدوءة لكل حساب في اليوم بتوقيت UTC (المهمة الفاشلة تعيد وحدتها، والمهمة المعاد استخدامها لا تستهلك أي وحدة)، إضافة إلى حد المعدّل في الدقيقة الخاص بخطتك. بحد أقصى أحدث 20,000 عملية التقاط لكل مهمة (truncated).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scope": {
                    "type": "string",
                    "enum": [
                      "own",
                      "company"
                    ],
                    "default": "own"
                  },
                  "org": {
                    "type": "string",
                    "description": "مع scope company: إحدى الشركات التي تملكها (الحالية أو الأخيرة)، عندما تملك كلتيهما."
                  },
                  "language": {
                    "type": "string",
                    "default": "en",
                    "description": "لغة ترجمة README: يوضع README.<language>.txt (الترجمة الكاملة) بجانب README.txt الإنجليزي في كل جزء؛ والقيمة en تعطي README.txt فقط. إحدى القيم: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. يُتسامح مع حالة الأحرف والواصل والمنطقة (\"PT-BR\" تعني pt_BR)؛ أما \"pt\" و\"zh\" وحدهما وأي قيمة أخرى فتعيد 400 MALFORMED_REQUEST. لا يُعاد استخدام التصدير الجاهز إلا للغة نفسها. ترسل لوحة التحكم لغة واجهتها."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "تصديرك الجاهز لا يزال محدَّثًا ويُعاد استخدامه: لا مهمة جديدة ولا وحدة يومية. اقرأ روابطه بطلب GET على poll_path.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job_id",
                    "status",
                    "scope",
                    "poll_path",
                    "reused"
                  ],
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "example": "cexp_3Fh8sKq0aZr2LmN1"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "ready"
                      ]
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "poll_path": {
                      "type": "string",
                      "example": "/v1/captures/export-jobs/cexp_3Fh8sKq0aZr2LmN1"
                    },
                    "reused": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "بدأت المهمة.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job_id",
                    "status",
                    "scope",
                    "poll_path"
                  ],
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "example": "cexp_3Fh8sKq0aZr2LmN1"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "poll_path": {
                      "type": "string",
                      "example": "/v1/captures/export-jobs/cexp_3Fh8sKq0aZr2LmN1"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: جسم الطلب ليس كائن JSON، أو أن scope / org غير صالح.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: أُرسل الطلب بمفتاح اختبار، أو بقيمة scope company من شخص ليس مالك شركة ضمن نافذة التنزيل الخاصة بها.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "EXPORT_JOB_RUNNING: لا تزال إحدى مهام الحساب قيد المعالجة؛ ويحددها details.job_id وdetails.poll_path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/export-jobs/{job_id}": {
      "get": {
        "tags": [
          "عمليات الالتقاط"
        ],
        "operationId": "getCaptureExportJob",
        "summary": "حالة مهمة تصدير عمليات الالتقاط وروابط تنزيلها",
        "description": "حالة المهمة وتقدّمها؛ وبمجرد أن تصبح جاهزة، رابط موقَّع مسبقًا واحد لكل جزء. يظل الرابط صالحًا حتى url_expires_at: لمدة 24 ساعة كحد أقصى، ولا يتجاوز أبدًا download_expires_at، ولا يتجاوز أبدًا صلاحية بيانات الاعتماد التي وقّعته (وقد تقل مدته عن ساعة)؛ استعلم مرة أخرى بعد url_expires_at للحصول على روابط جديدة. يحل download_expires_at بعد 7 أيام من منتصف الليل بتوقيت UTC السابق لبدء الجزء الأول من المهمة (أي بعد 6 إلى 7 أيام من جاهزية المهمة)؛ وتُحذف الأجزاء بعده. تفقد المهمة تنزيلها مبكرًا (status بالقيمة expired، وتُحذف الأجزاء، مع expired_reason) عندما يحل محلها تصدير أحدث للنطاق نفسه، أو تُحذف نهائيًا عملية التقاط تضمها، أو تُحذف بيانات شركتها، أو لم تعد صلاحياتك تشملها (يُفحص ذلك في كل قراءة لمهمة جاهزة). المهمة التي لا تُبلغ عن أي تقدم لمدة 30 دقيقة يُنهيها هذا الاستدعاء بالحالة failed ويُحذف ما كتبته. تُسجَّل أول استجابة تتضمن روابط في كل ساعة في سجل التدقيق (capture.export_job.links_issued). لا يرى المهمة إلا الحساب الذي بدأها؛ وتصدير الشركة الذي يضم أرشيف شركة لا يُرى إلا عبر جلسة لوحة التحكم.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^cexp_[A-Za-z0-9]{16}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "المهمة.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job_id",
                    "status",
                    "scope",
                    "parts"
                  ],
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "example": "cexp_3Fh8sKq0aZr2LmN1",
                      "pattern": "^cexp_[A-Za-z0-9]{16}$"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing",
                        "ready",
                        "failed",
                        "expired"
                      ],
                      "description": "processing أثناء كتابة خطة التصدير والأجزاء؛ ready مع روابط التنزيل؛ failed (ابدأ مهمة جديدة، وقد أُعيدت وحدة اليوم)؛ expired عندما تنتهي نافذة التنزيل أو يُسحب التنزيل (expired_reason؛ وتُحذف الأجزاء)."
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "language": {
                      "type": "string",
                      "enum": [
                        "en",
                        "de",
                        "it",
                        "fr",
                        "es",
                        "pt_BR",
                        "cs",
                        "el",
                        "hi",
                        "hu",
                        "id",
                        "ja",
                        "ko",
                        "nl",
                        "pl",
                        "ro",
                        "ru",
                        "sv",
                        "tr",
                        "vi",
                        "zh_CN",
                        "zh_TW",
                        "ar",
                        "he",
                        "da",
                        "fi",
                        "nb",
                        "sk",
                        "uk"
                      ],
                      "description": "لغة README في المهمة (README.<language>.txt إلى جانب README.txt؛ وen عند وجود README.txt فقط)."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "ended_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "progress": {
                      "type": "object",
                      "nullable": true,
                      "properties": {
                        "captures_total": {
                          "type": "integer",
                          "nullable": true
                        },
                        "captures_done": {
                          "type": "integer"
                        },
                        "parts_total": {
                          "type": "integer",
                          "nullable": true
                        },
                        "parts_done": {
                          "type": "integer"
                        }
                      }
                    },
                    "truncated": {
                      "type": "boolean",
                      "description": "true عندما يملك الحساب أكثر من 20,000 عملية التقاط قابلة للتصدير: تضم المهمة أحدث 20,000 منها."
                    },
                    "counts": {
                      "type": "object",
                      "nullable": true,
                      "description": "يُعيَّن عندما تكون المهمة جاهزة.",
                      "properties": {
                        "captures": {
                          "type": "integer",
                          "description": "صفوف captures.csv."
                        },
                        "packaged": {
                          "type": "integer",
                          "description": "الحزم الموجودة في الأجزاء."
                        },
                        "failed": {
                          "type": "integer",
                          "description": "عمليات الالتقاط الفاشلة (بلا حزمة، ومدرجة في captures.csv فقط)."
                        },
                        "unavailable": {
                          "type": "integer",
                          "description": "الحزم غير المضمَّنة: تعذّرت قراءتها أثناء تشغيل التصدير، أو يتجاوز حجمها 2 GB (يوضح captures.csv أيها)."
                        }
                      }
                    },
                    "parts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "part",
                          "bytes",
                          "count",
                          "download_url"
                        ],
                        "properties": {
                          "part": {
                            "type": "integer",
                            "minimum": 1,
                            "example": 1,
                            "description": "رقم الجزء، من 1 إلى عدد الأجزاء."
                          },
                          "bytes": {
                            "type": "integer",
                            "example": 1834201112,
                            "description": "حجم ملف ZIP للجزء بالبايت."
                          },
                          "count": {
                            "type": "integer",
                            "example": 412,
                            "description": "حزم عمليات الالتقاط في هذا الجزء."
                          },
                          "filename": {
                            "type": "string",
                            "example": "proofsnap-captures-2026-10-07T1430Z-part-1-of-3.zip",
                            "description": "اسم الملف الذي يُحفظ به التنزيل (Content-Disposition): proofsnap-captures-<تاريخ ووقت بدء المهمة بتوقيت UTC>، مع -part-N-of-M عند وجود عدة أجزاء."
                          },
                          "download_url": {
                            "type": "string",
                            "format": "uri",
                            "nullable": true,
                            "description": "طلب GET موقَّع مسبقًا لملف ZIP الخاص بالجزء، صالح حتى url_expires_at. null عندما تكون المهمة منتهية الصلاحية."
                          }
                        }
                      }
                    },
                    "download_expires_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "نهاية نافذة التنزيل: بعد 7 أيام من منتصف الليل بتوقيت UTC السابق لبدء الجزء الأول (تحذف دورة حياة التخزين الأجزاء بعدها)."
                    },
                    "url_expires_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "متى تتوقف قيم download_url في هذه الاستجابة عن العمل: بعد 24 ساعة كحد أقصى، ولا تتجاوز أبدًا download_expires_at، ولا تتجاوز أبدًا صلاحية بيانات اعتماد التوقيع. استعلم مرة أخرى بعد ذلك للحصول على روابط جديدة."
                    },
                    "error": {
                      "type": "string",
                      "nullable": true,
                      "example": "export timed out"
                    },
                    "expired_reason": {
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "window_ended",
                        "superseded",
                        "rights_changed",
                        "capture_erased",
                        "company_deleted",
                        null
                      ],
                      "description": "يُعيَّن عندما تكون قيمة status هي expired: window_ended (انقضى download_expires_at)، superseded (تصدير أحدث للنطاق نفسه)، rights_changed (لم تعد صلاحياتك تشمل المهمة)، capture_erased (حُذفت نهائيًا عملية التقاط تضمها المهمة)، company_deleted (حُذفت بيانات شركتها)."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "EXPORT_JOB_NOT_FOUND: لا توجد مهمة كهذه، أو أنها مهمة حساب آخر، أو انتهت صلاحية سجلها (بعد 8 أيام من انتهائها)، أو أُرسل الطلب بمفتاح اختبار.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/webhook-subscriptions": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookSubscriptions",
        "summary": "عرض اشتراكات webhook",
        "description": "اشتراكات webhook الخاصة بالحساب، الأحدث أولًا، النشطة والمعطّلة، دون الأسرار. مفتاح API أو جلسة لوحة التحكم؛ ويمكن استخدام مفتاح للقراءة فقط (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "الاشتراكات.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscriptions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookSubscription"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "createWebhookSubscription",
        "summary": "الاشتراك في أحداث الحساب",
        "description": "اشترك بنقطة نهاية (endpoint) تعمل عبر https في أحداث الحساب: يُرسَل إليها بطلب POST كل حدث مطابق في الحساب، موقَّعًا بسر الاشتراك الخاص به (يُعاد مرة واحدة في هذه الاستجابة). يتطلب عنوان بريد إلكتروني مؤكَّدًا (403 EMAIL_NOT_VERIFIED). مسموح به حتى عندما يكون الاشتراك متأخر السداد (لا يُفوتَر شيء). حدود الاشتراكات النشطة لكل حساب: 25 على developer وstartup وgrowth وcustom وlegal_solo وlegal_seats، و10 على website_archive، و0 على archive_only وsandbox (409 WEBHOOK_SUBSCRIPTION_LIMIT). يتبع عنوان URL قواعد webhook_url (https، دون بيانات اعتماد، ودون عنوان خاص أو محجوز؛ ويُفحص مجددًا عند كل تسليم). يُدعم Idempotency-Key: إعادة المحاولة بالمفتاح نفسه وجسم الطلب نفسه خلال 24 ساعة تعيد الاستجابة الأولى، بما فيها السر، بدلًا من إنشاء اشتراك ثانٍ. مفتاح API أو جلسة لوحة التحكم؛ ويمكن استخدام مفتاح للقراءة فقط (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "additionalProperties": false,
                "properties": {
                  "url": {
                    "type": "string",
                    "example": "https://hooks.example.com/proofsnap"
                  },
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 9,
                    "uniqueItems": true,
                    "items": {
                      "$ref": "#/components/schemas/WebhookEventType"
                    }
                  },
                  "filters": {
                    "$ref": "#/components/schemas/WebhookSubscriptionFilters"
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "نص عادي، بحد أقصى 200 حرف."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "تم الإنشاء. لا يظهر `secret` إلا هنا.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscriptionCreated"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (events أو filters أو description أو حقل غير معروف)، أو MISSING_FIELD (url)، أو INVALID_URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "EMAIL_NOT_VERIFIED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "WEBHOOK_SUBSCRIPTION_LIMIT، IDEMPOTENCY_IN_PROGRESS.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED (يشير عنوان URL إلى عنوان محظور) أو IDEMPOTENCY_KEY_REUSED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/webhook-subscriptions/{subscription_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/SubscriptionId"
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "getWebhookSubscription",
        "summary": "جلب اشتراك webhook",
        "description": "اشتراك واحد مع حالة التسليم الخاصة به، دون السر. مفتاح API أو جلسة لوحة التحكم؛ ويمكن استخدام مفتاح للقراءة فقط (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "الاشتراك.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscription"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "WEBHOOK_SUBSCRIPTION_NOT_FOUND: غير معروف، أو محذوف، أو يخص حسابًا آخر.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "updateWebhookSubscription",
        "summary": "تعديل اشتراك webhook أو إعادة تفعيله",
        "description": "عدّل الأحداث أو المرشّحات (القيمة null أو {} تمسحها) أو الوصف، أو أعد تفعيل اشتراك معطّل بإرسال status بالقيمة \"active\" (يعيد ضبط عدّادات الإخفاق؛ ويُفحص مقابل حد الاشتراكات النشطة). لا يمكن تغيير عنوان URL: أنشئ اشتراكًا جديدًا. يتطلب عنوان بريد إلكتروني مؤكَّدًا. مفتاح API أو جلسة لوحة التحكم؛ ويمكن استخدام مفتاح للقراءة فقط (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 9,
                    "uniqueItems": true,
                    "items": {
                      "$ref": "#/components/schemas/WebhookEventType"
                    }
                  },
                  "filters": {
                    "type": "object",
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/WebhookSubscriptionFilters"
                      }
                    ],
                    "nullable": true
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 200,
                    "nullable": true
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "الاشتراك بعد تعديله.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscription"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "EMAIL_NOT_VERIFIED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "WEBHOOK_SUBSCRIPTION_NOT_FOUND: غير معروف، أو محذوف، أو يخص حسابًا آخر.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "WEBHOOK_SUBSCRIPTION_LIMIT (إعادة التفعيل فوق الحد).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhookSubscription",
        "summary": "حذف اشتراك webhook",
        "description": "يحذف الاشتراك وسره وعمليات التسليم المنتظرة في قائمة الانتظار. 404 لاشتراك غير معروف أو محذوف مسبقًا (اعتبره غير موجود). مفتاح API أو جلسة لوحة التحكم؛ ويمكن استخدام مفتاح للقراءة فقط (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "تم الحذف.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "WEBHOOK_SUBSCRIPTION_NOT_FOUND: غير معروف، أو محذوف، أو يخص حسابًا آخر.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/events": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listEvents",
        "summary": "سجل الأحداث",
        "description": "كل حدث صدر للحساب خلال آخر 30 يومًا، سواء استلمه اشتراك أم لا، الأحدث أولًا. يرى مفتاح API أحداث البيئة التي يعمل فيها (المفتاح الفعلي يرى الأحداث الفعلية، ومفتاح الاختبار يرى أحداث الاختبار)؛ وترى جلسة لوحة التحكم الأحداث الفعلية. استخدمه لاستكمال الأحداث الفائتة أو للاستعلام الدوري بدلًا من الاشتراك. مفتاح API أو جلسة لوحة التحكم؛ ويمكن استخدام مفتاح للقراءة فقط (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "نوع الحدث هذا فقط.",
            "schema": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "من 1 إلى 100، والافتراضي 25.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "معرّف حدث: تبدأ الصفحة بعده (مرِّر next_cursor). المعرّف الذي يخص حسابًا آخر أو بيئة أخرى، أو الأقدم من 30 يومًا، يعيد 400.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "صفحة من الأحداث.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true,
                      "description": "مرِّره بوصفه starting_after للحصول على الصفحة التالية؛ null في الصفحة الأخيرة."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (type أو limit أو starting_after).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/whoami": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "whoami",
        "summary": "من أنا",
        "description": "ما هي بيانات الاعتماد: الخطة، والبيئة، وهل مفتاح API للقراءة فقط، ومعرّف المفتاح واسمه وبادئته (null لجلسة لوحة التحكم). تستخدمه عمليات التكامل لاختبار الاتصال ولتسميته. لا يتضمن عنوان بريد إلكتروني ولا معرّف حساب. مفتاح API أو جلسة لوحة التحكم؛ ويمكن استخدام مفتاح للقراءة فقط (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "بيانات الاعتماد.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plan": {
                      "type": "string",
                      "nullable": true,
                      "example": "startup",
                      "enum": [
                        "developer",
                        "startup",
                        "growth",
                        "custom",
                        "legal_solo",
                        "legal_seats",
                        "website_archive",
                        "archive_only",
                        "sandbox",
                        null
                      ]
                    },
                    "api_read_only": {
                      "type": "boolean",
                      "description": "true لمفتاح API لحساب Website Archive (يمكنه القراءة والاشتراك، لا إنشاء عمليات التقاط)."
                    },
                    "env": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ],
                      "description": "البيئة التي تعمل فيها بيانات الاعتماد."
                    },
                    "key": {
                      "type": "object",
                      "nullable": true,
                      "properties": {
                        "key_id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string",
                          "nullable": true
                        },
                        "prefix": {
                          "type": "string",
                          "enum": [
                            "ps_live_",
                            "ps_test_"
                          ]
                        }
                      }
                    },
                    "email_verified": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/events": {
      "get": {
        "tags": [
          "عمليات الالتقاط"
        ],
        "operationId": "listCaptureEvents",
        "summary": "سلسلة أحداث عمليات الالتقاط (عمليات الحذف والحفظ القانوني)",
        "description": "سجل الحساب الذي يكشف أي تعديل لما جرى لعمليات الالتقاط الخاصة به: النقل إلى سلة المهملات، والاستعادة، والحذف النهائي (erase)، وتفريغ سلة المهملات، والحذف بانتهاء مدة الاحتفاظ، والحذف مع الحساب أو الشركة، ووضع الحفظ القانوني ورفعه (رفع النظام للحفظ بعد انتهاء الاشتراك يحمل actor `system` وdetail `subscription_ended`)، ونتيجة S3 Object Lock، واستيراد حزمة أدلة (type `import`، وdetail = نوع الحزمة). الأقدم أولًا. تبقى السلسلة بعد زوال عمليات الالتقاط وبيانات الحساب: فهي تحتفظ بالمعرّفات وبصمات ملفات البيان فقط، دون أي محتوى من عمليات الالتقاط ودون أي نص حر.\n\nللتحقق منها: (1) لكل حدث احسب SHA-256 على JSON بصيغته المعيارية (canonical) للحقول التي تُحسب بصمتها (verification.hashed_fields؛ المفاتيح مرتبة، دون مسافات بيضاء، والقيم الغائبة بوصفها null) وقارنه بـ `hash`؛ (2) تحقق من أن كل `prev_hash` يساوي `hash` الخاص بالحدث السابق (64 صفرًا عندما تكون seq هي 1) ومن عدم وجود فجوات في seq؛ (3) بالنسبة إلى التثبيت (anchor)، تحقق من أن `head_hash` يساوي `hash` الخاص بالحدث `to_seq`، واكتب `ots_proof_base64` في ملف (بعد فك ترميز base64) وشغّل `ots verify -d <head_hash> anchor.ots` (عميل OpenTimestamps): فهذا يثبت أن السلسلة حتى ذلك الحدث كانت موجودة في موعد لا يتجاوز وقت كتلة Bitcoin. تُنشأ عمليات التثبيت مرة يوميًا لكل حساب لديه أحداث من يوم سابق.\n\nمفتاح API أو جلسة لوحة التحكم، للحساب نفسه؛ ويمنح `scope=company` مالك الشركة سلسلة عمليات الالتقاط الخاصة بالشركة (يختار `org` إحداها عندما يملك المستدعي عدة شركات؛ والشركة المحفوظة كأرشيف شركة عبر جلسة لوحة التحكم فقط). يحصل مفتاح الاختبار على 403 FORBIDDEN: فالسلسلة تشمل عمليات التقاط فعلية ولا يمكن تصفيتها دون كسرها.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "من 1 إلى 500، والافتراضي 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "قيمة seq لحدث: تبدأ الصفحة بعده (مرِّر next_starting_after).",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "own",
                "company"
              ],
              "default": "own"
            }
          },
          {
            "name": "org",
            "in": "query",
            "required": false,
            "description": "scope=company: معرّف الشركة.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "صفحة من السلسلة.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "chain": {
                      "type": "string"
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "head": {
                      "type": "object",
                      "properties": {
                        "chain": {
                          "type": "string"
                        },
                        "seq": {
                          "type": "integer"
                        },
                        "head_hash": {
                          "type": "string",
                          "nullable": true
                        },
                        "anchored_seq": {
                          "type": "integer"
                        }
                      }
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CaptureEvent"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_starting_after": {
                      "type": "integer",
                      "nullable": true
                    },
                    "anchors": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CaptureEventAnchor"
                      }
                    },
                    "verification": {
                      "type": "object",
                      "properties": {
                        "hash": {
                          "type": "string"
                        },
                        "hashed_fields": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "genesis_prev_hash": {
                          "type": "string"
                        },
                        "anchor": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: قيمة limit أو starting_after أو scope غير صالحة.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: أُرسل الطلب بمفتاح اختبار، أو بقيمة scope=company من شخص لا يملك الشركة.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/legal-holds": {
      "post": {
        "tags": [
          "عمليات الالتقاط"
        ],
        "operationId": "bulkLegalHold",
        "summary": "وضع الحفظ القانوني أو رفعه بشكل جماعي",
        "description": "يضع (enabled true) أو يرفع (false) الحفظ القانوني لعدد كبير من عمليات الالتقاط دفعة واحدة. يحتوي selector على واحد فقط من: `capture_ids` (من 1 إلى 500 معرّف)، أو `matter_id` (عمليات الالتقاط في قضية يستطيع المستدعي فتحها)، أو `host` (عمليات الالتقاط الخاصة بالمستدعي لاسم المضيف هذا)، أو `schedule_id` (عمليات الالتقاط الخاصة بالمستدعي التي أُجريت بأحد جداوله). تمر كل عملية التقاط بقواعد PATCH /captures/{capture_id} مع الإجراء legal_hold وتحصل على نتيجتها الخاصة: applied، أو unchanged (في تلك الحالة بالفعل)، أو الرفض مع رمز الخطأ (مثل FORBIDDEN وCAPTURE_IN_TRASH وCAPTURE_PROCESSING وLEGAL_HOLD_OWNER_ONLY وLEGAL_HOLD_MATTER، وLEGAL_HOLD_SOURCE لنسخة منقّحة لا تزال عملية الالتقاط المصدر لها تحت الحفظ، وCAPTURE_NOT_FOUND، وLEGAL_HOLD_NOT_AVAILABLE لعملية التقاط في وضع الاختبار). النسخ المنقّحة المُنشأة من كل عملية التقاط تتبع حالة حفظها، وكذلك لعملية التقاط أُبلغ عنها بأنها unchanged؛ وعندما لا يكتمل ذلك ضمن الطلب، فإنه يكتمل خلال دقائق. يتطلب وضع الحفظ خطة مدفوعة: يحصل وضع الاختبار (sandbox) المجاني على 403 LEGAL_HOLD_NOT_AVAILABLE (details.reason \"plan\") للطلب بأكمله؛ أما الرفع فيعمل على كل الخطط. الحفظ الموضوع هنا يحمل legal_hold_source \"bulk\"؛ ويعمل رفعه بشكل جماعي أو واحدًا تلو الآخر بالطريقة نفسها. بحد أقصى 500 عملية التقاط لكل طلب: القضية أو المضيف أو الجدول الذي يضم أكثر من ذلك يُقتطع (`truncated: true`، أرسل الطلب مرة أخرى). يُطبَّق الحفظ القانوني في S3 Object Lock لأول 25 عملية التقاط متغيّرة ضمن الطلب، والبقية خلال دقائق (`s3_legal_hold.status` pending). التكلفة: وحدة واحدة من حد المعدّل لكل 10 عمليات التقاط (وحدة واحدة على الأقل)؛ والطلب الذي يحتاج أكثر مما تسمح به نافذة زمنية واحدة يعيد 400 RATE_LIMIT_COST_TOO_HIGH. مفتاح API أو جلسة لوحة التحكم؛ على Website Archive يكون مفتاح API للقراءة فقط (403 PLAN_API_READ_ONLY). تحصل جلسة لوحة التحكم أو المفتاح الذي لم يُؤكَّد عنوان بريده الإلكتروني على 403 EMAIL_NOT_VERIFIED. لا يحظره اشتراك متأخر السداد.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled",
                  "selector"
                ],
                "additionalProperties": false,
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "نص عادي يُحفظ مع كل حفظ قانوني يوضع (لا مع الرفع)؛ ولا يدخل أبدًا في السلاسل التي تُحسب بصمتها."
                  },
                  "selector": {
                    "type": "object",
                    "minProperties": 1,
                    "maxProperties": 1,
                    "properties": {
                      "capture_ids": {
                        "type": "array",
                        "minItems": 1,
                        "maxItems": 500,
                        "items": {
                          "type": "string"
                        }
                      },
                      "matter_id": {
                        "type": "string"
                      },
                      "host": {
                        "type": "string"
                      },
                      "schedule_id": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "النتائج لكل عملية التقاط.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    },
                    "selector": {
                      "type": "string",
                      "enum": [
                        "capture_ids",
                        "matter_id",
                        "host",
                        "schedule_id"
                      ]
                    },
                    "requested": {
                      "type": "integer"
                    },
                    "truncated": {
                      "type": "boolean"
                    },
                    "applied": {
                      "type": "integer"
                    },
                    "unchanged": {
                      "type": "integer"
                    },
                    "refused": {
                      "type": "integer"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "capture_id": {
                            "type": "string"
                          },
                          "result": {
                            "type": "string",
                            "enum": [
                              "applied",
                              "unchanged",
                              "refused"
                            ]
                          },
                          "code": {
                            "type": "string",
                            "description": "رمز الخطأ في حالة الرفض."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (جسم الطلب، أو selector، أو أكثر من 500 من capture_ids)، RATE_LIMIT_COST_TOO_HIGH.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "EMAIL_NOT_VERIFIED، PLAN_API_READ_ONLY، LEGAL_HOLD_NOT_AVAILABLE (الوضع دون خطة مدفوعة، details.reason plan).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND (قيمة matter_id لقضية لا يستطيع المستدعي فتحها)، SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/imports": {
      "post": {
        "tags": [
          "استيراد الحزم"
        ],
        "operationId": "createImport",
        "summary": "بدء استيراد حزمة",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "ينشئ عملية استيراد ويبدأ رفعها. أرسل الحجم الدقيق لملف ZIP بالبايت؛ تتضمن الاستجابة عنوان URL واحدًا موقَّعًا مسبقًا لطلب PUT لكل جزء بحجم 32 MiB (الأخير أصغر)، صالحًا لمدة ساعة واحدة (`upload.expires_at`؛ ولا يتجاوز أبدًا صلاحية بيانات اعتماد التوقيع لدى الخادم). أرسل بايتات كل جزء بطلب PUT إلى عنوانه، واحتفظ بالترويسة `ETag` من كل استجابة، ثم استدعِ POST /imports/{import_id}/complete خلال 24 ساعة. مع `matter_id` تُضاف عملية الالتقاط المقبولة إلى تلك القضية: يجب أن تكون قادرًا على إضافة عمليات التقاط إليها الآن (قضية مفتوحة تستطيع فتحها؛ واستيراد مقعد في شركة لا يكون إلا إلى قضية تابعة لتلك الشركة). كل إنشاء يُحتسب ضمن الحد اليومي البالغ 50.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "filename",
                  "size_bytes"
                ],
                "additionalProperties": false,
                "properties": {
                  "filename": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "اسم الملف المعروض (نص عادي: بلا أحرف تحكم أو أحرف غير مرئية أو أحرف اتجاه النص)."
                  },
                  "size_bytes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 2147483648,
                    "description": "الحجم الدقيق لملف ZIP بالبايت، 2 GiB على الأكثر."
                  },
                  "matter_id": {
                    "type": "string",
                    "description": "اختياري: أضف عملية الالتقاط المقبولة إلى هذه القضية."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "أُنشئت عملية الاستيراد؛ ارفع الأجزاء.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "import_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "uploading"
                      ]
                    },
                    "upload": {
                      "type": "object",
                      "properties": {
                        "upload_id": {
                          "type": "string"
                        },
                        "part_size": {
                          "type": "integer",
                          "example": 33554432
                        },
                        "parts": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "part_number": {
                                "type": "integer"
                              },
                              "url": {
                                "type": "string",
                                "format": "uri",
                                "description": "عنوان URL موقَّع مسبقًا لعملية S3 UploadPart: أرسل عبر PUT عدد size_bytes من البايتات بالضبط (يوقّع عنوان URL قيمة Content-Length)، ثم اقرأ ترويسة الاستجابة ETag. لا حاجة إلى أي ترويسة أخرى."
                              },
                              "size_bytes": {
                                "type": "integer",
                                "description": "الطول الدقيق لهذا الجزء بالبايت: part_size لكل جزء عدا الأخير، والباقي للجزء الأخير (يبدأ الجزء n عند (n - 1) x part_size)."
                              }
                            }
                          }
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (body، filename، size_bytes، matter_id، حقل غير معروف).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: الاشتراك متأخر السداد أو غير مدفوع.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "IMPORT_NOT_ALLOWED (details.reason plan: خطط Legal فقط؛ live_credentials_required: مفتاح اختبار، أو جلسة بلا حساب فوترة مرتبط، أو وضع الاختبار (sandbox))، EMAIL_NOT_VERIFIED، FORBIDDEN (استيراد يجريه صاحب مقعد في شركة إلى قضية تابعة لشركة أخرى).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "IMPORT_LIMIT (توجد 3 عمليات استيراد قيد التنفيذ؛ details.limit، details.in_progress)، MATTER_SEALED، MATTER_ARCHIVED، COMPANY_READ_ONLY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "IMPORT_TOO_LARGE: قيمة size_bytes تتجاوز 2 GiB (details.max_bytes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: بلغت الحد لكل دقيقة، أو الحد اليومي البالغ 50 عملية استيراد (details.action import_create، details.daily_limit).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "get": {
        "tags": [
          "استيراد الحزم"
        ],
        "operationId": "listImports",
        "summary": "عرض عمليات الاستيراد الخاصة بك",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "عمليات الاستيراد الخاصة بك، الأحدث أولًا. تُحفظ عمليات الاستيراد التي بلغت حالة نهائية لمدة 30 يومًا (وتبقى عملية الالتقاط الخاصة بالاستيراد المقبول). يظهر الرفع الذي تجاوز مهلته البالغة 24 ساعة بالحالة expired.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "قيمة next_cursor من الصفحة السابقة."
          }
        ],
        "responses": {
          "200": {
            "description": "صفحة من عمليات الاستيراد.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "imports": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Import"
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (limit، cursor).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/imports/{import_id}": {
      "parameters": [
        {
          "name": "import_id",
          "in": "path",
          "required": true,
          "description": "معرّف عملية الاستيراد (imp_ متبوعًا بـ 16 إلى 24 حرفًا ورقمًا). يُرجع المعرّف سيئ الصياغة 404 IMPORT_NOT_FOUND.",
          "schema": {
            "type": "string",
            "pattern": "^imp_[A-Za-z0-9]{16,24}$"
          }
        }
      ],
      "get": {
        "tags": [
          "استيراد الحزم"
        ],
        "operationId": "getImport",
        "summary": "جلب عملية استيراد",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "عملية الاستيراد، ونتيجتها بعد أن تبلغ حالة نهائية. استعلم عنها دوريًا بعد POST /imports/{import_id}/complete؛ يستغرق التحقق عادةً ثواني، وقد تستغرق حزمة حجمها 2 GiB بضع دقائق. مع ?upload=true لرفعٍ لا يزال جاريًا، تضيف الاستجابة upload: عناوين URL جديدة موقَّعة مسبقًا (parts) للأجزاء التي لا يحتفظ بها التخزين بعد، والأجزاء المستلمة بالفعل (received: part_number، etag، size_bytes؛ وتكون null إذا تعذّر سردها، وعندئذٍ تُوقَّع جميع الأجزاء مسبقًا من جديد)، أو completed: true إذا كان الرفع قد اكتمل بالفعل (أرسل complete مرة أخرى). استخدمه لاستئناف الرفع بعد انقطاع الشبكة، أو انتهاء صلاحية عنوان URL لجزء (تصلح العناوين لمدة ساعة واحدة على الأكثر، ونافذة الرفع 24 ساعة)، أو إعادة تحميل الصفحة. يستهلك ?upload=true وحدة واحدة من حد المعدّل لكل دقيقة.",
        "responses": {
          "200": {
            "description": "عملية الاستيراد.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Import"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "IMPORT_NOT_FOUND (وكذلك لعملية استيراد تابعة لحساب آخر).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED (مع ?upload=true فقط).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        },
        "parameters": [
          {
            "name": "upload",
            "in": "query",
            "required": false,
            "description": "true (أو 1): أضف عناوين URL جديدة للأجزاء والأجزاء المستلمة لرفعٍ جارٍ.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "1"
              ]
            }
          }
        ]
      },
      "delete": {
        "tags": [
          "استيراد الحزم"
        ],
        "operationId": "cancelImport",
        "summary": "إلغاء عملية رفع",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "يلغي عملية استيراد لا يزال رفعها جاريًا: يُوقَف الرفع، وتُحذف الأجزاء، وتُزال عملية الاستيراد (يُرجع GET بعد ذلك 404).",
        "responses": {
          "200": {
            "description": "أُلغيت.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "import_id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "IMPORT_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "IMPORT_STATE: لم تعد عملية الاستيراد في مرحلة الرفع (details.status).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/imports/{import_id}/complete": {
      "parameters": [
        {
          "name": "import_id",
          "in": "path",
          "required": true,
          "description": "معرّف عملية الاستيراد (imp_ متبوعًا بـ 16 إلى 24 حرفًا ورقمًا). يُرجع المعرّف سيئ الصياغة 404 IMPORT_NOT_FOUND.",
          "schema": {
            "type": "string",
            "pattern": "^imp_[A-Za-z0-9]{16,24}$"
          }
        }
      ],
      "post": {
        "tags": [
          "استيراد الحزم"
        ],
        "operationId": "completeImport",
        "summary": "إكمال الرفع والتحقق",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "يُكمل الرفع ويبدأ التحقق. الحقل parts اختياري: أرسل كل رقم جزء (من 1 إلى N، بالترتيب) مع قيمة ETag التي أرجعها طلب PUT لذلك الجزء، أو احذفه ليقرأ الخادم الأجزاء المرفوعة من التخزين (مفيد في متصفح لا يستطيع قراءة ترويسة ETag). يتحقق من أن الحجم المخزَّن يساوي size_bytes. استعلم دوريًا عن GET /imports/{import_id}. قائمة الأجزاء التي يرفضها التخزين، أو جزء لم يُرفع بعد (details.missing_parts)، تُرجع 400 ويبقى الرفع مفتوحًا: ارفع ما ينقص (يمنحك GET /imports/{import_id}?upload=true عناوين URL جديدة) وأرسل الطلب مرة أخرى. إعادة إرسال complete بعد ضياع الاستجابة آمنة: يُرجع طلب complete الذي لا يزال قيد التنفيذ 409، والطلب الذي توقف يتولاه الطلب الجديد بعد مرور دقيقتين، وينتقل الرفع الذي اكتمل بالفعل إلى التحقق. الحجم المختلف عن size_bytes يؤدي إلى إلغاء الرفع وحذف ما رُفع (422 IMPORT_SIZE_MISMATCH؛ تُرفض عملية الاستيراد، فأنشئ عملية جديدة).",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "parts": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "part_number",
                        "etag"
                      ],
                      "properties": {
                        "part_number": {
                          "type": "integer",
                          "minimum": 1
                        },
                        "etag": {
                          "type": "string",
                          "description": "قيمة ترويسة الاستجابة ETag لطلب PUT الخاص بالجزء، مع علامات الاقتباس أو بدونها."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "بدأ التحقق.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "import_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "verifying"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (الأجزاء ليست بالترتيب أو لا تطابق الأجزاء المرفوعة؛ أو، دون parts، يوجد جزء لم يُرفع بعد: details.missing_parts).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "IMPORT_NOT_ALLOWED، EMAIL_NOT_VERIFIED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "IMPORT_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "IMPORT_STATE: لم تعد عملية الاستيراد في مرحلة الرفع (details.status، على سبيل المثال verifying أو expired).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "IMPORT_SIZE_MISMATCH (details.expected، details.actual).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "أرسل مفتاح API بالصيغة `Authorization: Bearer ps_live_...` (اسم المخطط لا يتأثر بحالة الأحرف). تُرجع الترويسة المفقودة أو سيئة الصياغة 401 MISSING_API_KEY. ويُرجع المفتاح غير المعروف أو المُبطَل أو منتهي الصلاحية، أو الحساب الذي لا يملك صلاحية الوصول إلى API، الخطأ 401 INVALID_API_KEY مع `WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`. الرمز 403 مخصص لبيانات اعتماد صالحة غير مسموح لها بتنفيذ إجراء ما (FORBIDDEN، قيود الخطة). يعمل مفتاح `ps_live_` الخاص بحساب لا يرتبط به حساب فوترة (عميل Stripe) كمفتاح اختبار: تحمل عمليات الالتقاط علامة مائية ولا تُحتسب عليها رسوم أبدًا، ويُرفض include_eidas بالخطأ 403 EIDAS_REQUIRES_LIVE_KEY. تخزّن البوابة القرار الخاص بالمفتاح مؤقتًا لمدة تصل إلى 60 ثانية، لذا قد يستمر المفتاح المُبطَل في العمل لمدة تصل إلى 60 ثانية. إذا تعذّر التحقق من بيانات الاعتماد بسبب عطل مؤقت في الخادم الخلفي، تُرجع البوابة بدلًا من ذلك 503 SERVICE_UNAVAILABLE؛ ولا تُخزَّن هذه الاستجابة مؤقتًا، فأعد المحاولة مع تأخير متزايد. يُعاد تحدي `WWW-Authenticate` دون تغيير في استجابة 401 الصادرة عن البوابة (التحقق من بيانات الاعتماد)؛ أما في استجابة 401 الصادرة عن معالج المسار فتسلّم البوابة التحدي نفسه بعد إعادة تسميته إلى `x-amzn-Remapped-WWW-Authenticate`."
      }
    },
    "headers": {
      "XRequestId": {
        "description": "معرّف الطلب، ومفيد أيضًا في طلبات الدعم. يُرسَل في الاستجابات الناجحة؛ وتحمله أجسام الأخطاء في الحقل request_id.",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitLimit": {
        "description": "عدد الطلبات المسموح بها في النافذة الزمنية الحالية البالغة 60 ثانية. ترويسة قديمة، محتفَظ بها إلى جانب RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "عدد الطلبات المتبقية في النافذة الزمنية الحالية. ترويسة قديمة، محتفَظ بها إلى جانب RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "وقت انتهاء النافذة الزمنية الحالية بتنسيق ISO 8601. ترويسة قديمة، محتفَظ بها إلى جانب RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "RetryAfter": {
        "description": "عدد الثواني التي يجب انتظارها قبل إعادة المحاولة.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaLimit": {
        "description": "عمليات الالتقاط المضمّنة في الخطة شهريًا (المفاتيح الفعلية في الخطط ذات الحصة الشهرية).",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaRemaining": {
        "description": "عمليات الالتقاط المضمّنة المتبقية هذا الشهر.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaUsed": {
        "description": "عمليات الالتقاط المكتملة هذا الشهر.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaCap": {
        "description": "الحد الأقصى الشهري المطبَّق على المستدعي (القيمة الافتراضية للخطة، أو الحد الذي وضعه بنفسه، أو حد الشركة).",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaOrgLimit": {
        "description": "لأعضاء الشركة فقط: الحد الشهري على مستوى الشركة كلها، إذا حدده المالك.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaOrgUsed": {
        "description": "لأعضاء الشركة فقط: عمليات الالتقاط المقبولة للشركة كلها هذا الشهر.",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxLimit": {
        "description": "وضع الاختبار (sandbox) (جلسة في لوحة التحكم بلا خطة): رصيد دائم من عمليات الالتقاط الاختبارية التي تحمل علامة مائية (50).",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxRemaining": {
        "description": "وضع الاختبار (sandbox): الرصيد المتبقي.",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxUsed": {
        "description": "وضع الاختبار (sandbox): الرصيد المستخدَم.",
        "schema": {
          "type": "integer"
        }
      },
      "IdempotentReplay": {
        "description": "\"true\" عندما تعيد هذه الاستجابة نتيجة طلب سابق يحمل مفتاح Idempotency-Key نفسه.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      },
      "WWWAuthenticate": {
        "description": "تحدٍّ وفق RFC 6750 يُرسَل مع كل استجابة 401. في استجابة 401 يُرجعها معالج المسار لا البوابة، يسلّمه API Gateway بعد إعادة تسميته إلى `x-amzn-Remapped-WWW-Authenticate`. مفتاح API غير صالح أو مُبطَل أو منتهي الصلاحية، أو رمز جلسة غير صالح أو مُبطَل (INVALID_API_KEY): `Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`. ترويسة Authorization مفقودة أو سيئة الصياغة (MISSING_API_KEY): `Bearer realm=\"ProofSnap API\"`.",
        "schema": {
          "type": "string"
        },
        "example": "Bearer realm=\"ProofSnap API\", error=\"invalid_token\""
      },
      "RateLimitPolicy": {
        "description": "سياسة حد المعدّل، وفق مسودة IETF المسماة draft-ietf-httpapi-ratelimit-headers (بصيغة الحقل المنظَّم؛ تستند إلى مسودة وقد تتغير بتغيرها): `\"default\";q=<requests per window>;w=60`.",
        "schema": {
          "type": "string"
        },
        "example": "\"default\";q=120;w=60"
      },
      "RateLimit": {
        "description": "الحالة الحالية لحد المعدّل، وفق مسودة IETF المسماة draft-ietf-httpapi-ratelimit-headers (تستند إلى مسودة): `\"default\";r=<requests left>;t=<seconds until the window resets>`. تُرسَل في استجابات المسارات الخاضعة لحد المعدّل (عمليات الالتقاط المفردة والمجمّعة، وcompare، وredact، وcertify-hash) بعد تشغيل المحدِّد، لا في 429 فقط (الطلب المرفوض قبل المحدِّد، على سبيل المثال بسبب المصادقة، لا يحملها).",
        "schema": {
          "type": "string"
        },
        "example": "\"default\";r=117;t=42"
      }
    },
    "parameters": {
      "CaptureId": {
        "name": "capture_id",
        "in": "path",
        "required": true,
        "description": "معرّف عملية الالتقاط أو الشهادة أو المقارنة أو التنقيح أو الحزمة المستوردة. تبدأ المعرّفات بـ cap_ أو cert_ أو cmp_ أو red_ أو imp_. يُرجع المعرّف سيئ الصياغة 404 CAPTURE_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "cap_a1B2c3D4e5F6g7H8"
        }
      },
      "ScheduleId": {
        "name": "schedule_id",
        "in": "path",
        "required": true,
        "description": "معرّف الجدول. يُرجع الجدول التابع لحساب آخر 404 SCHEDULE_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "sch_a1B2c3D4e5F6g7H8"
        }
      },
      "MatterId": {
        "name": "matter_id",
        "in": "path",
        "required": true,
        "description": "معرّف القضية. تُرجع القضية التي لست عضوًا فيها 404 MATTER_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "mat_a1B2c3D4e5F6g7H8"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "مفتاح اختياري من العميل يجعل إعادة المحاولة آمنة. يُستخدم أول 255 حرفًا فقط، ويقتصر نطاق المفاتيح على حسابك لمدة 24 ساعة. تُرجع إعادة المحاولة بالمفتاح نفسه والجسم نفسه جسم الاستجابة الأصلي مع `Idempotent-Replay: true` (202 لعملية التقاط مفردة أو دفعة أو مقارنة، و201 للتنقيح؛ ويُحدَّث `status` إلى الحالة الحالية لعملية الالتقاط أو المقارنة أو التنقيح، ولكل عملية التقاط في الدفعة)؛ ولا تُنشأ عملية التقاط جديدة ولا تُحتسب عليها رسوم. يُرجع المفتاح نفسه مع جسم مختلف 422 IDEMPOTENCY_KEY_REUSED. تُرجع إعادة المحاولة أثناء استمرار قبول الطلب الأول 409 IDEMPOTENCY_IN_PROGRESS مع المعرّف الأصلي في `error.details.capture_id` (وقيمة batch_id في حالة الدفعة).",
        "schema": {
          "type": "string",
          "maxLength": 255
        }
      },
      "SubscriptionId": {
        "name": "subscription_id",
        "in": "path",
        "required": true,
        "description": "معرّف اشتراك webhook. يُرجع الاشتراك التابع لحساب آخر 404 WEBHOOK_SUBSCRIPTION_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "whs_a1B2c3D4e5F6g7H8"
        }
      }
    },
    "responses": {
      "RateLimited": {
        "description": "RATE_LIMITED: طلبات كثيرة جدًا في النافذة الزمنية الحالية البالغة 60 ثانية. يكرر `error.details.retry_after_seconds` قيمة Retry-After. تُبلغ RateLimit بالقيمة r=0 وبعدد الثواني حتى إعادة ضبط النافذة.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "RATE_LIMITED",
                "message": "Too many requests. See Retry-After header.",
                "details": {
                  "retry_after_seconds": 42
                }
              },
              "request_id": "..."
            }
          }
        }
      },
      "InternalError": {
        "description": "INTERNAL_ERROR: خطأ غير متوقع في الخادم. أعد المحاولة مع تأخير متزايد أُسيًّا.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "AuthUnavailable": {
        "description": "SERVICE_UNAVAILABLE: تعذّر على البوابة التحقق من بيانات الاعتماد (عطل مؤقت في الخادم الخلفي). لا تُخزَّن الاستجابة مؤقتًا: أعد المحاولة مع تأخير متزايد.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "SERVICE_UNAVAILABLE",
                "message": "Authentication is temporarily unavailable. Please retry."
              },
              "request_id": "..."
            }
          }
        }
      },
      "EidasOrAuthUnavailable": {
        "description": "EIDAS_UNAVAILABLE: طُلب include_eidas لكن الطوابع الزمنية المؤهلة غير مهيّأة في هذا النشر؛ أعد المحاولة دون include_eidas. SERVICE_UNAVAILABLE: تعذّر على البوابة التحقق من بيانات الاعتماد (عطل مؤقت في الخادم الخلفي، ولا تُخزَّن الاستجابة مؤقتًا)؛ أعد المحاولة مع تأخير متزايد.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "401، رمزان. MISSING_API_KEY: ترويسة Authorization مفقودة أو سيئة الصياغة (`WWW-Authenticate: Bearer realm=\"ProofSnap API\"`). INVALID_API_KEY: أُرسلت بيانات الاعتماد لكنها غير صالحة: مفتاح API غير معروف أو مُبطَل أو منتهي الصلاحية، أو حساب لا يملك صلاحية الوصول إلى API، أو رمز جلسة غير صالح أو مُبطَل (`WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`). لا تُعِد المحاولة ببيانات الاعتماد نفسها. يعني 403 أن بيانات الاعتماد صالحة لكن غير مسموح لها بهذا الإجراء (FORBIDDEN، قيود الخطة).",
        "headers": {
          "WWW-Authenticate": {
            "$ref": "#/components/headers/WWWAuthenticate"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "missing": {
                "value": {
                  "error": {
                    "code": "MISSING_API_KEY",
                    "message": "Missing or malformed Authorization header. Send Authorization: Bearer <your API key>."
                  },
                  "request_id": "..."
                }
              },
              "invalid": {
                "value": {
                  "error": {
                    "code": "INVALID_API_KEY",
                    "message": "The API key is invalid, revoked or expired, or the account has no API access."
                  },
                  "request_id": "..."
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "كل الأخطاء بهذا الشكل. تحمل استجابات الأخطاء الترويسة `X-Request-ID` بالقيمة نفسها الموجودة في `request_id`.\n\nحجم الطلب: يمكن أن يبلغ جسم الطلب 6 MB على الأكثر (حد الحمولة في Lambda؛ أما API Gateway نفسه فيتوقف عند 10 MB). يُرفض الجسم الأكبر قبل أن يصل إلى شيفرة API، لذا لا تتخذ تلك الاستجابة هذا الشكل.\n\n| HTTP | الرمز | المعنى |\n| --- | --- | --- |\n| 400 | `MALFORMED_REQUEST` | الجسم ليس JSON صالحًا (الرسالة \"Request body is not valid JSON\")، أو الطلب غير صالح: عندما يذكر `details.reason` الحقل والسبب، تكرر `message` هذا السبب، وإلا فهي \"The request is invalid.\" |\n| 400 | `MISSING_FIELD` | حقل مطلوب مفقود. |\n| 400 | `INVALID_URL` | عنوان URL ليس عنوان http(s) صالحًا، أو يتجاوز طوله 2048 حرفًا، أو أن webhook_url لا يستخدم https. |\n| 400 | `INVALID_HASH` | certify-hash: بصمة ليست 64 حرفًا ست عشريًا. |\n| 400 | `INVALID_TIMESTAMP_TYPE` | قيمة options.timestamp_type ليست \"bitcoin\". |\n| 400 | `INVALID_VIEWPORT` | قيمة options.viewport ليست \"desktop\" أو \"mobile\". |\n| 400 | `METADATA_TOO_LARGE` | يحتوي options.metadata على أكثر من 10 مفاتيح، أو على قيمة ليست نصية، أو على قيمة تتجاوز 500 حرف. |\n| 400 | `INVALID_EVIDENCE_PACK_OPTIONS` | قيمة options.evidence_pack غير صالحة. |\n| 400 | `INVALID_BATCH` | الحقل urls في الدفعة ليس مصفوفة من 2 إلى 50 عنوان URL صالحًا. |\n| 400 | `REDACTION_NO_OPS` | لا يتضمن التنقيح أي عمليات، أو لم تطابق أيٌّ منها شيئًا. |\n| 400 | `INVALID_CRON` | تعذّر تحليل cron_expr. |\n| 400 | `INTERVAL_TOO_SHORT` | وتيرة الجدول أقل من الحد الأدنى للخطة. |\n| 400 | `RATE_LIMIT_COST_TOO_HIGH` | يحتاج الطلب إلى وحدات من حد المعدّل أكثر مما تسمح به نافذة زمنية واحدة؛ `details.max_per_window`، `details.window_seconds`، `details.requested`. قسّم الطلب. |\n| 400 | `WEBHOOK_REQUIRES_API_KEY` | أُرسل `webhook_url` من جلسة في لوحة التحكم. تُوقَّع رسائل webhook بسر webhook الخاص بمفتاح API، لذا تحتاج إلى مفتاح API. |\n| 401 | `MISSING_API_KEY` | ترويسة Authorization مفقودة أو سيئة الصياغة. `WWW-Authenticate: Bearer realm=\"ProofSnap API\"`. |\n| 401 | `INVALID_API_KEY` | المفتاح غير معروف أو مُبطَل أو منتهي الصلاحية، أو لا توجد صلاحية وصول إلى API، أو رمز الجلسة غير صالح أو مُبطَل. `WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`. |\n| 402 | `QUOTA_EXCEEDED` | بلغت الحد الأقصى الشهري أو رصيد وضع الاختبار (sandbox) (ويظهر أيضًا في `error` لعملية توثيق في الخلفية رُفضت لهذا السبب). |\n| 402 | `PAYMENT_REQUIRED` | الاشتراك الفعلي متأخر السداد أو غير مدفوع (ويظهر أيضًا في `error` لعملية التقاط مجدولة جرى تخطيها لهذا السبب). |\n| 403 | `FORBIDDEN` | المورد يخص شخصًا آخر، أو الإجراء مقصور على المالك. الرسالة: \"You do not have access to this resource.\" (يوضح details.reason المزيد حيث يفيد ذلك). |\n| 403 | `EMAIL_NOT_VERIFIED` | حاولت جلسة في لوحة التحكم لم يُؤكَّد عنوان بريدها الإلكتروني إنشاء شيء دائم أو مدفوع أو يرسل دعوات إلى أشخاص (عمليات الالتقاط، والدفعات، والمقارنات، والتنقيحات، ومفاتيح API، وأسرار webhook، وصفحة الدفع checkout، وتغييرات الخطة، وتغييرات المقاعد، وcertify-hash، والجداول، ودعوات الشركة، والقضايا وكل تغيير عليها، وأعضاء القضايا، وعمليات الالتقاط المسندة إلى القضايا، والمشاركات وإلغاؤها، والأختام، والتصديرات، وحالات الحفظ القانوني). تبقى القراءة متاحة. الرسالة: \"Confirm your email address first. Use the link we sent you, or send a new one from the dashboard.\" ويُرفض مفتاح API بالطريقة نفسها إذا لم يُؤكَّد عنوان البريد الإلكتروني لحسابه. |\n| 403 | `EIDAS_REQUIRES_LIVE_KEY` | أُرسل include_eidas مع مفتاح اختبار (يُعدّ مفتاح ps_live_ الخاص بحساب لا يرتبط به حساب فوترة مفتاح اختبار)، أو لمقارنة أو تنقيح عملية التقاط اختبارية (`details.reason`). يحصل المفتاح الفعلي أو الجلسة التي تعمل في وضع الاختبار لعدم ارتباط حساب فوترة على `details.reason` \"no_billing_account\". |\n| 403 | `MATTERS_NOT_ENABLED` | تتطلب القضايا خطة Legal. |\n| 403 | `SCHEDULE_LIMIT_REACHED` | بلغت حد الجداول النشطة في الخطة (أو أن الخطة لا تتضمن جداول). |\n| 403 | `ON_CHANGE_REQUIRES_PLAN` | لا تسمح الخطة بمراقبة on_change. |\n| 403 | `PLAN_ARCHIVE_ONLY` | تُبقي خطة Archive only أدلتك قابلة للقراءة والتنزيل والتحقق؛ ولا تُنشئ عمليات التقاط أو جداول أو عمليات توثيق أو مقارنات أو تنقيحات. |\n| 403 | `PLAN_NOT_AVAILABLE` | لا يمكن لهذا الحساب اختيار الخطة: تُشترى Archive only بعد انتهاء اشتراك API مدفوع (ما دامت الأدلة لا تزال محفوظة)، أو يُنتقل إليها بتغيير الخطة من developer أو startup أو growth أو website_archive أو legal_solo؛ ولا يختار أرشيف الشركة إلا مالك شركة انتهى اشتراكها، خلال نافذة التنزيل الخاصة بها. يوضح `details.reason` السبب. |\n| 403 | `PLAN_API_READ_ONLY` | خطة Website Archive: مفتاح API للقراءة فقط (السرد، والتنزيل، والتحقق، ورسائل webhook). أنشئ عمليات الالتقاط والجداول وعمليات التوثيق في لوحة التحكم، أو استخدم خطة API. |\n| 403 | `LEGAL_HOLD_OWNER_ONLY` | لا يرفع الحفظ القانوني عن عملية التقاط تابعة للشركة إلا مالك الشركة (يمكن لصاحب المقعد فرضه). |\n| 403 | `LEGAL_HOLD_NOT_AVAILABLE` | يتطلب فرض الحفظ القانوني خطة مدفوعة (`details.reason` \"plan\": وضع الاختبار (sandbox) المجاني، أو حساب انتهى اشتراكه) وعملية التقاط فعلية (`details.reason` \"test_capture\": عملية التقاط في وضع الاختبار تحمل علامة مائية). لا يُرفض رفع الحفظ أبدًا لهذا السبب. |\n| 403 | `IMPORT_NOT_ALLOWED` | يتطلب استيراد الحزم خطة Legal (`details.reason` \"plan\") ومفتاح API فعليًا أو جلسة في لوحة التحكم مرتبطة بحساب فوترة (`details.reason` \"live_credentials_required\"). |\n| 404 | `CAPTURE_NOT_FOUND` | عملية الالتقاط غير موجودة، أو حُذفت، أو أن المعرّف سيئ الصياغة. |\n| 404 | `MATTER_NOT_FOUND` | القضية غير موجودة أو لست عضوًا فيها. |\n| 404 | `IMPORT_NOT_FOUND` | عملية الاستيراد غير موجودة، أو تخص حسابًا آخر، أو أن المعرّف سيئ الصياغة. |\n| 404 | `WEBHOOK_SUBSCRIPTION_NOT_FOUND` | اشتراك webhook غير موجود، أو حُذف، أو يخص حسابًا آخر. |\n| 404 | `SCHEDULE_NOT_FOUND` | الجدول غير موجود أو يخص حسابًا آخر. |\n| 404 | `NOT_FOUND` | مورد آخر غير موجود (تنسيق البصمة في السجل العام، أو رمز المشاركة، أو مهمة التصدير، أو العضو). |\n| 404 | `EXPORT_JOB_NOT_FOUND` | مهمة تصدير عمليات الالتقاط غير موجودة، أو تخص حسابًا آخر، أو انتهت صلاحيتها، أو طُلبت بمفتاح اختبار. |\n| 405 | `METHOD_NOT_ALLOWED` | طريقة HTTP غير مسموح بها لهذا المسار. |\n| 409 | `CAPTURE_PROCESSING` | لا تزال عملية الالتقاط قيد المعالجة ولا يمكن حذفها (أو تغيير الحفظ القانوني لها) بعد. |\n| 409 | `CAPTURE_LEGAL_HOLD` | عملية الالتقاط خاضعة للحفظ القانوني ولا يمكن حذفها. |\n| 409 | `CAPTURE_IN_TRASH` | الحفظ القانوني: عملية الالتقاط في سلة المهملات؛ استعِدها أولًا. |\n| 409 | `CAPTURE_HOLD_GRACE` | حذف نهائي خلال المهلة التالية لرفع الحفظ القانوني عن عملية الالتقاط؛ `details.until`. النقل إلى سلة المهملات مسموح. |\n| 409 | `LEGAL_HOLD_MATTER` | فُرض الحفظ على عملية الالتقاط عبر الحفظ القانوني لقضيتها، وهو لا يزال قائمًا: ارفعه على مستوى القضية (`details.matter_id` لمن يملك صلاحية قراءة القضية). |\n| 409 | `LEGAL_HOLD_SOURCE` | يتبع الحفظ على التنقيح أو المقارنة عمليةَ الالتقاط التي أُنشئ منها (وفي المقارنة، أيًّا من عمليتي الالتقاط المقارَنتين)، وهي لا تزال خاضعة للحفظ القانوني (المفروض عليها مباشرة أو عبر قضيتها): تبقى الحزمة محفوظة حتى يُرفع ذلك الحفظ (`details.source_capture_id`). |\n| 409 | `COMPANY_READ_ONLY` | انتهى اشتراك الشركة (أو أنها محفوظة كأرشيف للشركة): لا يمكن إلا فتح قضاياها وتصديرها وإلغاء روابط مشاركتها، ولا شيء غير ذلك، ولا يُشتق أي شيء جديد من عمليات الالتقاط الخاصة بها. |\n| 409 | `WEBHOOK_SUBSCRIPTION_LIMIT` | بلغت حد اشتراكات webhook النشطة في الخطة (25 في خطط API وLegal، و10 في Website Archive، و0 في Archive only ووضع الاختبار (sandbox))؛ `details.limit`، `details.active`. |\n| 409 | `WEBHOOK_SECRET_ROTATION_PENDING` | لا يزال سر webhook السابق ضمن فترة التداخل البالغة 24 ساعة؛ `details.previous_secret_expires_at`. |\n| 409 | `IDEMPOTENCY_IN_PROGRESS` | لا يزال الطلب الأول الذي يحمل مفتاح Idempotency-Key هذا قيد القبول. |\n| 409 | `COMPARE_NOT_READY` | يجب أن تكتمل عمليتا الالتقاط كلتاهما قبل المقارنة. |\n| 409 | `REDACTION_NOT_READY` | يجب أن تكتمل عملية الالتقاط المصدر قبل التنقيح. |\n| 409 | `MATTER_SEALED` | القضية (أو قضية عملية الالتقاط) مختومة ولا يمكن تغييرها. |\n| 409 | `MATTER_SEALING` | يجري ختم القضية (60 ثانية على الأكثر): لا يمكن خلال ذلك إسناد عمليات التقاط، أو إعادة التسمية، أو تغيير الأعضاء، أو نقل عملية التقاط مرتبطة إلى سلة المهملات، أو ختم ثانٍ. `Retry-After`. |\n| 409 | `MATTER_ARCHIVED` | القضية مؤرشفة: لا عمليات التقاط جديدة، ولا إعادة تسمية، ولا أعضاء جدد، ولا مشاركة، ولا تصدير؛ أعد فتحها أولًا (PATCH status \"open\"). لا تزال إزالة عملية التقاط أو عضو ممكنة. |\n| 409 | `MATTER_CHANGED` | تغيّر سجل الحيازة أثناء الختم؛ لم يُختم شيء، أعد المحاولة. |\n| 409 | `MATTER_LINKED` | حذف عملية التقاط: عملية الالتقاط مستند مرفق في قضية لم يعد المستدعي عضوًا فيها. يزيلها مالك القضية من القضية أولًا. |\n| 409 | `MATTER_HAS_TRASHED_CAPTURES` | ختم القضية: توجد عملية التقاط من القضية في سلة المهملات. استعِدها أو أزلها من القضية، ثم اختم مرة أخرى. |\n| 409 | `MEMBER_HAS_INDIVIDUAL_SUBSCRIPTION` | دعوة إلى الشركة: لا يزال اشتراك API الخاص بالمدعو يتجدد. يلغيه المدعو، ثم يدعوه المالك مرة أخرى. |\n| 409 | `IMPORT_LIMIT` | توجد بالفعل 3 عمليات استيراد قيد التنفيذ (قيد الرفع أو التحقق)؛ `details.limit`، `details.in_progress`. |\n| 409 | `IMPORT_STATE` | عملية الاستيراد ليست في حالة تسمح بذلك (إكمال أو إلغاء عملية استيراد لم تعد في مرحلة الرفع)؛ `details.status`. |\n| 409 | `EXPORT_JOB_RUNNING` | لا يزال تصدير عمليات الالتقاط الخاصة بك قيد الإعداد؛ يحدده `details.job_id` و`details.poll_path`. ابدأ تصديرًا جديدًا بعد انتهائه. |\n| 413 | `IMPORT_TOO_LARGE` | يتجاوز حجم الحزمة 2 GiB؛ `details.max_bytes`. |\n| 413 | `VERIFY_PACKAGE_TOO_LARGE` | POST /captures/{capture_id}/verify: يتجاوز حجم الحزمة المخزَّنة 256 MiB؛ تحقّق منها دون اتصال باستخدام البرامج النصية المرفقة. `details.size_bytes`، `details.limit_bytes`. |\n| 422 | `IDEMPOTENCY_KEY_REUSED` | أُعيد استخدام Idempotency-Key مع جسم مختلف أو في بيئة مفتاح مختلفة (فعلي أو اختبار). |\n| 422 | `SSRF_BLOCKED` | يستهدف عنوان URL عنوانًا خاصًا أو عنوان loopback أو link-local أو عنوانًا محظورًا لسبب آخر. |\n| 422 | `TARGET_UNREACHABLE` | لا توجد سجلات DNS لاسم المضيف في عنوان URL، أو تعذّر تحليله. |\n| 422 | `REDACTION_SCREENSHOT_UNREADABLE` | تعذّر فك ترميز لقطة الشاشة لتنقيح المربعات. |\n| 422 | `REDACTION_LEAK_DETECTED` | بقي مصطلح منقَّح بعد التنقيح خارج عنوان URL لعملية الالتقاط؛ لم يصدر شيء. `details.files`، `details.rule_indexes`، `details.metadata_fields`، `details.in_capture_url`. |\n| 422 | `REDACTION_SOURCE_UNSUPPORTED` | لا يمكن تنقيح إلا عمليات التقاط صفحات الويب. `details.source` أو `details.manifest_type`. |\n| 422 | `REDACTION_SOURCE_INTEGRITY_FAILED` | ملف من عملية الالتقاط المصدر لا يطابق بصمة SHA-256 المدرجة في بيانها الموقَّع، أو مفقود؛ لم يصدر شيء. `details.mismatched`، `details.missing`. |\n| 422 | `COMPARE_PROFILE_MISMATCH` | POST /compare: أُجريت عمليتا الالتقاط بملف تعريف جهاز مختلف أو بإعدادات التقاط مختلفة؛ `details.differing`، `details.before`، `details.after`. أرسل allow_profile_mismatch: true للمقارنة رغم ذلك (تكون نتيجة النص والصورة عندئذٍ غير محددة). |\n| 422 | `COMPARE_URL_MISMATCH` | POST /compare: عمليتا الالتقاط لصفحتين مختلفتين؛ `details.before`، `details.after` (url وfinal_url في صورة origin والمسار). أرسل allow_url_mismatch: true للمقارنة رغم ذلك. |\n| 422 | `RULE902_NOT_AVAILABLE_FOR_MISMATCH` | POST /compare: طُلب include_rule_902 لعمليتي التقاط عُرضتا بشكل مختلف (allow_profile_mismatch)؛ `details.differing`. |\n| 422 | `COMPARE_SOURCE_INTEGRITY_FAILED` | ملف من عملية التقاط مصدر في POST /compare لا يطابق بصمة SHA-256 المدرجة في بيانها الموقَّع، أو مفقود؛ لم تُجرَ أي مقارنة ولم تُحتسب أي رسوم. `details.capture_id`، `details.mismatched`، `details.missing`، `details.sources[]`. |\n| 422 | `REDACTION_PATTERN_TOO_SLOW` | استغرق نمط تعبير نمطي في text_redactions وقتًا طويلًا جدًا على هذه الصفحة (تراجع كارثي)؛ لم يصدر شيء ولم تُحتسب أي رسوم. `details.rule_index`. بسّط النمط. |\n| 422 | `IMPORT_SIZE_MISMATCH` | لا يطابق حجم الحزمة المرفوعة قيمة size_bytes المعلنة؛ أُلغي الرفع وحُذف ما رُفع. `details.expected`، `details.actual`. |\n| 422 | `IMPORT_NOT_SUPPORTED` | مقارنة حزمة أدلة مستوردة أو تنقيحها. |\n| 429 | `RATE_LIMITED` | طلبات كثيرة جدًا في النافذة الزمنية الحالية، أو بلغت حدًا يوميًا لإنشاء القضايا (200)، أو الختم (20)، أو التصدير (50)، أو دعوات الأعضاء (50)، أو استيراد الحزم (50، `import_create`) (`details.daily_limit`، `details.action`). يحدد `Retry-After` موعد إعادة المحاولة. |\n| 429 | `CONCURRENT_CAPTURE_LIMIT` | عدد كبير جدًا من عمليات الالتقاط الخاصة بالحساب يعمل في الوقت نفسه (developer 10، startup 25، growth 100، legal_solo وlegal_seats 25، custom 200، website_archive 25، sandbox 5). تبدأ الدفعة بقدر ما يتسع من عناوين URL وتسرد الباقي في `not_admitted`؛ وإذا لم تتوفر خانة شاغرة يُرفض الطلب كله. `details.limit`، `details.in_flight`، `details.requested`، `Retry-After`. |\n| 429 | `TEST_MODE_DAILY_LIMIT` | العمل في وضع الاختبار (مفتاح اختبار أو جلسة بلا حساب فوترة مرتبط؛ يحمل علامة مائية ولا تُحتسب عليه رسوم) محدود بـ 200 وحدة لكل حساب في اليوم وفق UTC (تُحتسب الدفعة بعدد عناوين URL فيها). `details.used`، `details.limit`، `details.reset_at`، `Retry-After`. |\n| 429 | `IMPORT_DAILY_BYTES_LIMIT` | استيراد الحزم: بلغ حجم الاستيراد للحساب في اليوم وفق UTC الحد المسموح (5 GiB من الأحجام المعلنة). `details.period`، `details.limit_bytes`، `details.used_bytes`، `details.requested_bytes`، `details.reset_at`. |\n| 429 | `IMPORT_MONTHLY_BYTES_LIMIT` | استيراد الحزم: بلغ حجم الاستيراد للحساب في الشهر وفق UTC الحد المسموح (20 GiB). التفاصيل نفسها. |\n| 500 | `INTERNAL_ERROR` | خطأ غير متوقع. أعد المحاولة مع تأخير متزايد. |\n| 500 | `CAPTURE_FAILED` | تعذّر بدء عملية الالتقاط، أو (في `error` لعملية التقاط فاشلة) تعذّر إكمالها. |\n| 500 | `CAPTURE_TIME_BUDGET_EXCEEDED` | في `error` لعملية التقاط فاشلة، أو في تنقيح متزامن: استنفد التشغيل الوقت المخصص له، ولم تصدر أي حزمة ولم تُحتسب أي رسوم. |\n| 500 | `CAPTURE_SCREENSHOT_TIMEOUT` | في `error` لعملية التقاط فاشلة: لم يُنهِ المتصفح عرض لقطة الشاشة: انتهت مهلة كل طرق التقاطها (لقطة واحدة، أو شرائح أصغر، أو لقطة الشاشة الخاصة بالمتصفح)، وكذلك المحاولة الوحيدة المتكررة في متصفح بُدئ من جديد حين سمح الوقت المتبقي بها. لم تُحتسب أي رسوم؛ حاول مرة أخرى. |\n| 500 | `CERTIFY_FAILED` | في `error` لعملية توثيق فاشلة: تعذّر إكمال التوثيق. |\n| 500 | `REDACTION_FAILED` | في `error` لعملية تنقيح فاشلة: تعذّر إكمال التنقيح. أما التنقيح المرفوض لسبب معروف فيحمل رمز ذلك السبب بدلًا من ذلك (على سبيل المثال REDACTION_LEAK_DETECTED). |\n| 500 | `TIMESTAMP_SERVICE_UNAVAILABLE` | ختم القضية: فشل التثبيت عبر OpenTimestamps؛ لم يتغير شيء، أعد المحاولة. |\n| 500 | `CONFIGURATION_ERROR` | فشلت خطوة على جانب الخادم أو أنها غير مهيّأة (`details.reason`). |\n| 503 | `SERVICE_UNAVAILABLE` | المصادقة غير متاحة مؤقتًا (تعذّر التحقق من بيانات الاعتماد). لا تُخزَّن الاستجابة مؤقتًا؛ أعد المحاولة. |\n| 503 | `EIDAS_UNAVAILABLE` | طُلب include_eidas لكن الطوابع الزمنية وفق eIDAS غير مهيّأة. |\n| 503 | `EIDAS_SEAL_RETRY` | ختم القضية: لم يصل الطابع الزمني وفق eIDAS في الوقت المحدد؛ لم يُختم شيء. أعد المحاولة بعد `Retry-After`؛ تعيد المحاولة استخدام الطابع الزمني الصادر بالفعل. |\n| 503 | `LIMIT_CHECK_UNAVAILABLE` | ختم القضية: تعذّر التحقق من حده اليومي؛ لم يُنفَّذ شيء. أعد المحاولة. |\n| 504 | `UPSTREAM_TIMEOUT` | لم تستجب خطوة معالجة متزامنة (certify-hash، تصدير القضية) في الوقت المحدد. قد تكتمل رغم ذلك: استعلم دوريًا عمّا يحدده `details` (`certify_id`، `poll_path`)، أو أعد المحاولة. |\n| 404 | `ORG_INVITE_NOT_FOUND` | دعوة إلى الشركة غير معروفة، أو موجهة إلى شخص آخر، أو لم تعد معلّقة، أو أن شركتها لم تعد تعمل (details.reason `company_inactive`). |\n| 409 | `ORG_INVITE_NO_SEATS` | قبول دعوة إلى الشركة: جميع مقاعد الشركة مستخدمة؛ تبقى الدعوة مفتوحة. |\n| 409 | `ORG_INVITE_IN_OTHER_ORG` | قبول دعوة إلى الشركة: يشغل المستدعي مقعدًا في شركة أخرى؛ تبقى الدعوة مفتوحة. |\n| 409 | `ORG_INVITE_HAS_SUBSCRIPTION` | قبول دعوة إلى الشركة: لا يزال اشتراك API الخاص بالمستدعي يتجدد؛ تبقى الدعوة مفتوحة. |\n| 409 | `ORG_OWNER_CANNOT_LEAVE` | حاول مالك الشركة مغادرة شركته. |\n| 410 | `ORG_INVITE_EXPIRED` | قبول دعوة إلى الشركة مضى عليها أكثر من 7 أيام. |",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "رمز خطأ مقروء آليًا (UPPER_SNAKE_CASE)."
              },
              "message": {
                "type": "string",
                "description": "رسالة مقروءة للبشر."
              },
              "details": {
                "type": "object",
                "additionalProperties": true,
                "description": "سياق إضافي اختياري، مثل `reason` أو `field` أو `retry_after_seconds`."
              }
            },
            "required": [
              "code",
              "message"
            ]
          },
          "request_id": {
            "type": "string",
            "description": "معرّف الطلب."
          }
        },
        "example": {
          "error": {
            "code": "INVALID_URL",
            "message": "URL is not a valid http(s) URL",
            "details": {
              "reason": "Only http and https schemes are allowed"
            }
          },
          "request_id": "..."
        }
      },
      "CaptureOptions": {
        "type": "object",
        "description": "خيارات عملية الالتقاط. جميع الحقول اختيارية.",
        "properties": {
          "scroll_full_page": {
            "type": "boolean",
            "description": "التقاط الصفحة القابلة للتمرير بأكملها. القيمة false تلتقط منطقة العرض فقط.",
            "default": true
          },
          "expand_collapsed": {
            "type": "boolean",
            "description": "افتح الأقسام المطوية قبل لقطة الشاشة: عناصر <details> المغلقة، وأزرار الأكورديون والإفصاح (aria-expanded مع aria-controls)، وعناصر التحكم النصية من نوع «see more» / «show replies». مع ضوابط حماية: لا شيء داخل التنقل أو ترويسات الصفحة أو القوائم أو مربعات الحوار، ولا روابط إلى URL آخر، ولا إرسال لأي نموذج، ولا تسميات تغيّر الحالة (like، follow، buy، delete، ...)، وكل عنصر تحكم مرة واحدة على الأكثر، وعنصر التحكم الذي يحاول مغادرة الصفحة يوقف التوسيع ويُعاد تحميل URL الأصلي. تُسجَّل النتيجة لكل عنصر تحكم في forensic_log.json (page_modifications.expand_collapsed: ما عُثر عليه، وما فُتح، وما تُرك مغلقًا، وأسباب التخطي، والطلبات التي بدأت، وارتفاع الصفحة قبل وبعد) مع ملخص في metadata.json (expand_collapsed: requested، opened_total، left_closed_total)؛ ولا يذكر evidence.pdf والشهادات إلا ما فُتح فعلًا.",
            "default": true
          },
          "viewport": {
            "type": "string",
            "description": "desktop هو 1920x1080 بنسبة بكسلات 1؛ وmobile هو 390x844 بنسبة بكسلات 3 مع وكيل مستخدم (user agent) لمتصفح Safari على الجوال. القيم الأخرى تُرجع 400 INVALID_VIEWPORT.",
            "enum": [
              "desktop",
              "mobile"
            ],
            "default": "desktop"
          },
          "viewport_width": {
            "type": "number",
            "minimum": 320,
            "maximum": 3840,
            "description": "عرض مخصص لمنطقة العرض ببكسلات CSS. يجب إرساله مع viewport_height، وإلا يُرجَع 400 MALFORMED_REQUEST."
          },
          "viewport_height": {
            "type": "number",
            "minimum": 240,
            "maximum": 4320,
            "description": "ارتفاع مخصص لمنطقة العرض ببكسلات CSS. يجب إرساله مع viewport_width."
          },
          "device_scale_factor": {
            "type": "number",
            "minimum": 1,
            "maximum": 3,
            "description": "نسبة بكسلات الجهاز (device pixel ratio) في لقطة الشاشة."
          },
          "wait_for_selector": {
            "type": "string",
            "description": "ينتظر مدة تصل إلى 10 ثوانٍ حتى يظهر محدِّد CSS هذا قبل الالتقاط. إذا لم يظهر، تستمر عملية الالتقاط دون خطأ. يُسجَّل في forensic_log.json page_modifications.",
            "maxLength": 200
          },
          "wait_ms": {
            "type": "number",
            "minimum": 0,
            "maximum": 15000,
            "description": "انتظار إضافي بالمللي ثانية بعد تحميل الصفحة."
          },
          "screenshot_format": {
            "type": "string",
            "description": "مع jpeg تتضمن الحزمة screenshot.jpeg؛ ومع png تتضمن screenshot.png بلا فقدان. لا تُضاف C2PA Content Credentials إلا إلى لقطات الشاشة بتنسيق JPEG.",
            "enum": [
              "jpeg",
              "png"
            ],
            "default": "jpeg"
          },
          "url_stamp": {
            "type": "boolean",
            "description": "أضف فوق صورة الصفحة شريط ترويسة يعرض URL الملتقط، ووقت الالتقاط بتوقيت UTC (لحظة إنتاج المتصفح للقطة الشاشة؛ وفي الصفحة الطويلة الملتقطة على شرائح، لحظة إنتاج آخر شريحة) ومعرّف الدليل، مع وسمه بأنه تعليق توضيحي برمجي. يُضاف الشريط فوق الصورة، فلا يُغطّى أي بكسل من الصفحة. يسجّل chain_of_custody.json الحقل screenshot_url_header.raw_screenshot_sha256 مع raw_screenshot_source: في الصفحة الملتقطة دفعة واحدة (browser_image، browser_png، browser_jpeg_joined، browser_jpeg_reencoded) هو SHA-256 لصورة الصفحة كما أنتجها المتصفح، قبل الشريط؛ وفي الصفحة الطويلة الملتقطة على شرائح ثم المدمجة (joined_bands، joined_bands_lossless، joined_bands_reencoded) هو SHA-256 للصورة المدمجة كما كتبها ProofSnap مع الشريط، قبل أي C2PA Content Credentials. عندما تتعذّر إضافة الشريط (لأن الصورة معه ستتجاوز 65,535 px التي يتسع لها JPEG، أو فشل رسمه، أو، في مسار لقطة الشاشة الاحتياطي فقط، لأن صورة الصفحة تتجاوز 32,000,000 بكسل) تُسلَّم لقطة الشاشة بدونه: يسجّل metadata.json ذلك في screenshot.url_stamp_skipped، ويسجّله chain_of_custody.json في screenshot_url_header (applied false، مع السبب).",
            "default": false
          },
          "network_log": {
            "type": "boolean",
            "description": "تضمين network.har (ملف HAR يحتوي ترويسات الطلبات والاستجابات والتوقيتات، دون المحتوى) وnetwork.original.har.",
            "default": true
          },
          "warc": {
            "type": "boolean",
            "description": "تضمين capture.warc (ISO 28500) مع معاملات HTTP الخاصة بتحميل الصفحة. يُقرأ محتوى الاستجابات ضمن حد زمني (30 ثانية على الأكثر، وفقط ما دام الجزء الاختياري من الميزانية الزمنية لعملية الالتقاط يسمح بذلك، بعد الوقت المحجوز لـ eIDAS وC2PA وقراءة الساعة الختامية)؛ والمحتوى الذي يقطعه هذا الحد يُخزَّن فارغًا مع WARC-Truncated: time، والاستجابة التي لم يُوصل إليها تُستبعد، ويسجّل warc_captured في chain_of_custody.json (وwarc في forensic_log.json) القيم truncated وtruncation_reason وresponse_pairs وrecords_total والأعداد.",
            "default": false
          },
          "external_archive": {
            "type": "boolean",
            "description": "اطلب من خدمة Save Page Now التابعة لـ Internet Archive أرشفة URL (يحتفظ Internet Archive بتلك النسخة علنًا). تُصنَّف النتيجة بحسب الطلب الذي أنتج النسخة المؤرشفة وتوقيتها مقارنةً بالطلب: created (أُنشئت لهذا الطلب)، recent_existing (ردّ Internet Archive بنسخة مؤرشفة أقدم)، created_after_request (توجد نسخة مؤرشفة أحدث لكن الطلب لم يُؤكَّد)، existing (توجد نسخة مؤرشفة أقدم فقط)، pending، rate_limited، failed (رفض Internet Archive الطلب)، unconfirmed (لم يؤكد أي رد الطلب ولم يرفضه ولا تُعرف أي نسخة مؤرشفة)، unavailable، skipped، skipped_sensitive أو skipped_query. لا تُرسَل أبدًا عناوين URL التي يبدو أنها تحمل بيانات اعتماد أو تحمل معاملات أو أسماء معاملات أو مقاطع مسار تشبه الرموز المميزة (tokens). لا يُرسَل URL الذي يحتوي سلسلة استعلام إلا عند ضبط هذا الخيار على true صراحةً؛ ومع القيمة الافتراضية يُتخطّى (skipped_query). تُسجَّل النتيجة في forensic_log.json (external_archive، مع snapshot_offset_seconds مقارنةً بعملية الالتقاط) وفي chain_of_custody.json، وتُرجَع بوصفها external_archive عبر GET /captures/{capture_id}. اضبطه على false لعناوين URL الخاصة. يُرسَل الطلب فور تحميل الصفحة، بالتوازي مع بقية عملية الالتقاط. الفشل لا يُفشل عملية الالتقاط.",
            "default": true
          },
          "dismiss_consent": {
            "type": "boolean",
            "description": "اضبط مسبقًا ملفات تعريف الارتباط (cookies) الخاصة بالموافقة على «قبول الكل» قبل تحميل الصفحة، وحاول إغلاق لافتات الموافقة على ملفات تعريف الارتباط. هذا يغيّر الصفحة، لذا فهو معطّل افتراضيًا؛ ويُسجَّل ما نُفّذ في forensic_log.json consent_dismissal.",
            "default": false
          },
          "dismiss_popups": {
            "type": "boolean",
            "description": "أغلق النوافذ المنبثقة الخاصة بالنشرات البريدية والعروض الترويجية والطبقات المنبثقة المشابهة عبر عنصر الإغلاق الخاص بها. يُسجَّل عدد النوافذ المنبثقة المغلقة وعناصر التحكم المستخدمة في forensic_log.json page_modifications. تُسجَّل كل نافذة منبثقة مغلقة في forensic_log.json page_modifications (popups_dismissed، popups_dismissed_controls)؛ ويذكر evidence.pdf عدد ما أُغلق منها.",
            "default": false
          },
          "dark_mode": {
            "type": "boolean",
            "description": "اعرض الصفحة بنظام الألوان الداكن. يغيّر مظهر الصفحة؛ ويُسجَّل في forensic_log.json page_modifications. يُفصَح عنه في evidence.pdf وفي forensic_log.json page_modifications.",
            "default": false
          },
          "block_ads": {
            "type": "boolean",
            "description": "احظر الطلبات إلى مضيفي الإعلانات والتتبع المعروفين. يحذف جزءًا من المحتوى؛ ويُسجَّل في forensic_log.json page_modifications.",
            "default": false
          },
          "block_resources": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "image",
                "font",
                "media",
                "stylesheet"
              ]
            },
            "description": "أنواع الموارد المطلوب حظرها. يحذف جزءًا من المحتوى؛ ويُسجَّل في forensic_log.json page_modifications."
          },
          "inject_css": {
            "type": "string",
            "description": "CSS يُضاف إلى الصفحة قبل الالتقاط. يغيّر الصفحة؛ ويُخزَّن حرفيًا في forensic_log.json page_modifications. يُخزَّن دائمًا حرفيًا في forensic_log.json page_modifications (injected_css) مع injected_css_status بقيمة `applied` أو `failed` ومع injected_css_error. يذكر evidence.pdf أنه حُقن CSS مخصص؛ ولا يطبع الشيفرة.",
            "maxLength": 10000
          },
          "inject_js": {
            "type": "string",
            "description": "JavaScript يُشغَّل في الصفحة قبل الالتقاط. يغيّر الصفحة. يُخزَّن دائمًا حرفيًا في forensic_log.json page_modifications (injected_js) مع injected_js_status وinjected_js_error: `applied` (اكتمل تشغيله حتى النهاية)، `invalid` (لم يُصرَّف، مثلًا بسبب خطأ نحوي، فلم يُشغَّل قط ولم يغيّر الصفحة)، `failed` (رمى استثناءً أثناء التشغيل، فربما غيّر الصفحة قبل توقفه) أو `timed_out` (توقفت عملية الالتقاط عن انتظاره بعد 5 ثوانٍ؛ وربما استمر النص البرمجي في العمل وتغيير الصفحة). يذكر evidence.pdf النتيجة ويحيل إلى forensic_log.json، وتُدرج شهادة المنشأ الشيفرة المحقونة ضمن تعديلات الصفحة ما لم تكن `invalid`؛ ولا يطبع أيٌّ منهما الشيفرة.",
            "maxLength": 10000
          },
          "locale": {
            "type": "string",
            "description": "لغة المتصفح ومنطقته (locale) المطلوب محاكاتها، مثل de أو de-DE (تضبط لغة المتصفح وAccept-Language). محاكاة فقط: لا يُستخدم أي خادم وكيل (proxy)، لذا فالمحتوى الذي يعتمد على IP الزائر يظل يعكس منطقة خادم الالتقاط. يُسجَّل في metadata.json geo.",
            "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
          },
          "timezone": {
            "type": "string",
            "description": "المنطقة الزمنية للمتصفح المطلوب محاكاتها (اسم IANA، مثل Europe/Berlin). محاكاة فقط."
          },
          "language": {
            "type": "string",
            "description": "لغة ملفات PDF المخصصة للقراءة البشرية. إحدى القيم: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. يُتسامح مع حالة الأحرف والشرطة والمنطقة (تُقبل \"DE\" و\"fr-FR\" و\"PT-BR\")؛ وتُرفض \"pt\" و\"zh\" المجردتان. تعيد القيمة غير المدعومة 400 MALFORMED_REQUEST.",
            "default": "en",
            "example": "de"
          },
          "timestamp_type": {
            "type": "string",
            "description": "لا تُقبل إلا القيمة \"bitcoin\" (تُجرى محاولة الربط عبر OpenTimestamps دائمًا). أي قيمة أخرى تُرجع 400 INVALID_TIMESTAMP_TYPE. استخدم include_eidas للحصول على طابع زمني مؤهل.",
            "enum": [
              "bitcoin"
            ],
            "default": "bitcoin"
          },
          "include_eidas": {
            "type": "boolean",
            "description": "اطلب أيضًا طابعًا زمنيًا إلكترونيًا مؤهلًا وفق eIDAS (RFC 3161) على بصمة SHA-256 للملف manifest.json، ويُسلَّم بوصفه manifest.json.tsr (ومعه eidas_validation.json عند إرجاع بيانات التحقق). للمفاتيح الفعلية فقط: مفتاح الاختبار يُرجع 403 EIDAS_REQUIRES_LIVE_KEY. إذا لم تكن خدمة eIDAS مهيأة في النشر، يُرفض الطلب بالخطأ 503 EIDAS_UNAVAILABLE قبل احتساب أي رسوم. قبل استخدام الرمز (token)، يتحقق منه ProofSnap (التوقيع، والبصمة التي يحملها، وسلسلة الشهادات حتى جذر CA Disig QCA4، وحالة شهادة TSA) وتسجّل هذا التحقق في eidas_validation.json (verification). إذا فشل طلب الطابع الزمني أثناء عملية الالتقاط، أو لم يجتز الرمز هذا التحقق، تُسلَّم الحزمة بدونه مع ربط OpenTimestamps فقط، ولا يُفوتَر الطابع، ويذكر timestamps.eidas (صف عملية الالتقاط، وGET /captures/{capture_id}، وwebhook الاكتمال completed) الحالة status not_obtained مع السبب (token_refused أو unavailable أو time_budget). بدءًا من لقطة الشاشة، تترك الخطوات الاختيارية لعملية الالتقاط (محتوى WARC، وردود الأرشيف وWHOIS، وتوقيع C2PA، والقراءة الثانية للساعة) وقتًا للطابع، وتُقتطع أولًا. تُفوتَر الرسوم لكل طابع.",
            "default": false
          },
          "include_rule_902": {
            "type": "boolean",
            "description": "أضف rule_902_certification.pdf: قالب إقرار غير موقّع وفق [FRE 902(13)/(14)](https://www.govinfo.gov/content/pkg/USCODE-2023-title28/html/USCODE-2023-title28-app-federalru-dup2-rule902.htm) و[28 U.S.C. 1746](https://www.govinfo.gov/content/pkg/USCODE-2023-title28/html/USCODE-2023-title28-partV-chap115-sec1746.htm) يكمله شخص ويوقّعه. تُدرج بصمة SHA-256 الخاصة به في manifest.json. ويُضاف أيضًا عندما يحمل evidence_pack.custodian قيمة غير فارغة. إذا فشل إنشاؤه، تكتمل عملية الالتقاط بدونه. معطّل حاليًا: قالب الإقرار متوقف مؤقتًا، لذا يُقبل هذا الحقل ويُتجاهل (لا يُنشأ rule_902_certification.pdf ولا يُدرج في الحزمة) وتحمل الاستجابة التحذير RULE902_DISABLED.",
            "default": false
          },
          "case_reference": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "تسمية القضية الخاصة بك. تُدمج المسافات البيضاء المتتالية في مسافة واحدة. تخضع لقاعدة النص العادي (انظر PlainText). تُسجَّل في metadata.json وforensic_log.json وchain_of_custody.json، وتُطبع على evidence.pdf.",
            "maxLength": 200
          },
          "case_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "معرّف القضية في نظامك. يُسجَّل مثل case_reference؛ ويخضع لقاعدة النص العادي (انظر PlainText).",
            "maxLength": 200
          },
          "case_note": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "ملاحظة نصية حرة. تخضع لقاعدة النص العادي (انظر PlainText). تُسجَّل في metadata.json وforensic_log.json وchain_of_custody.json (legal_context.note).",
            "maxLength": 500
          },
          "webhook_url": {
            "type": "string",
            "description": "عنوان URL بنظام https يستقبل capture.completed أو capture.failed. انظر Webhooks. يجب أن يستخدم URL بروتوكول https وألا يشير إلى عنوان خاص أو loopback أو link-local أو carrier-grade NAT أو عنوان بيانات وصفية سحابية (القواعد نفسها المطبقة على URL عملية الالتقاط؛ وإلا 422 SSRF_BLOCKED). القيمة الفارغة أو المكونة من مسافات بيضاء فقط تعني عدم وجود webhook. يتطلب مفتاح API: إذا أُرسل من جلسة في لوحة التحكم يُرفض بالخطأ 400 WEBHOOK_REQUIRES_API_KEY، لأن رسائل webhook تُوقَّع بسر webhook الخاص بالمفتاح.",
            "format": "uri"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 500
            },
            "maxProperties": 10,
            "description": "حتى 10 وسوم نصية (القيم حتى 500 حرف)، تُرجَع مع عملية الالتقاط. المخالفات تُرجع 400 METADATA_TOO_LARGE."
          },
          "evidence_pack": {
            "type": "object",
            "properties": {
              "enabled": {
                "type": "boolean",
                "description": "القيمة false تستبعد صفحة الشهادة من evidence.pdf.",
                "default": true
              },
              "jurisdiction": {
                "type": "string",
                "description": "يُسجَّل في options ضمن forensic_log.json. ولا أثر آخر له.",
                "enum": [
                  "US-Federal",
                  "EU-eIDAS"
                ],
                "default": "US-Federal"
              },
              "custodian": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "مقبول؛ لا يُطبع في القالب، وتبقى حقول المُقِرّ فيه فارغة للشخص الذي يوقّعه.",
                    "maxLength": 200
                  },
                  "law_firm": {
                    "type": "string",
                    "description": "مقبول؛ لا يُطبع في القالب، وتبقى حقول المُقِرّ فيه فارغة للشخص الذي يوقّعه.",
                    "maxLength": 200
                  },
                  "role": {
                    "type": "string",
                    "description": "مقبول؛ لا يُستخدم في القالب.",
                    "maxLength": 200
                  },
                  "case_caption": {
                    "type": "string",
                    "description": "مقبول؛ لا يُستخدم في القالب.",
                    "maxLength": 200
                  }
                },
                "additionalProperties": false,
                "description": "تخضع كل قيمة لقاعدة النص العادي (انظر PlainText)؛ وإلا 400 INVALID_EVIDENCE_PACK_OPTIONS."
              }
            },
            "description": "القيم غير الصالحة تُرجع 400 INVALID_EVIDENCE_PACK_OPTIONS."
          }
        }
      },
      "CaptureRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "الصفحة المطلوب التقاطها. http أو https فقط، و2048 حرفًا على الأكثر. تُرفض العناوين الخاصة وعناوين loopback وlink-local والبيانات الوصفية بالخطأ 422 SSRF_BLOCKED، واسم المضيف الذي لا يملك سجلات DNS يُرجع 422 TARGET_UNREACHABLE.",
            "format": "uri",
            "maxLength": 2048
          },
          "options": {
            "$ref": "#/components/schemas/CaptureOptions"
          },
          "private_note": {
            "$ref": "#/components/schemas/PrivateNote"
          }
        },
        "example": {
          "url": "https://example.com",
          "options": {
            "case_reference": "Smith v. Jones",
            "webhook_url": "https://your-app.example/webhooks/proofsnap"
          }
        }
      },
      "CaptureAccepted": {
        "type": "object",
        "properties": {
          "capture_id": {
            "type": "string",
            "example": "cap_a1B2c3D4e5F6g7H8"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing"
            ]
          },
          "url": {
            "type": "string"
          },
          "poll_path": {
            "type": "string",
            "description": "مسار الحالة متضمنًا المرحلة /v1، مثل /v1/captures/cap_..."
          },
          "message": {
            "type": "string"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "private_note": {
            "type": "string",
            "description": "الملاحظة الخاصة كما خُزّنت (بعد التطبيع)، ولا تظهر إلا إذا أُرسلت."
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "CaptureResult": {
        "type": "object",
        "description": "عملية التقاط أو شهادة أو مقارنة أو تنقيح. تعتمد الحقول على status.",
        "properties": {
          "capture_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "completed",
              "failed"
            ]
          },
          "url": {
            "type": "string",
            "description": "URL الملتقط (null للشهادات والمقارنات).",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "batch_id": {
            "type": "string",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "type": {
            "type": "string",
            "description": "comparison في المقارنات، وimport في حزمة الأدلة المستوردة.",
            "enum": [
              "comparison",
              "import"
            ]
          },
          "compared": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "في المقارنات فقط: معرّفا عمليتي الالتقاط المصدريتين."
          },
          "diff_summary": {
            "$ref": "#/components/schemas/DiffSummary"
          },
          "estimated_completion_seconds": {
            "type": "integer",
            "description": "processing فقط: تقدير تقريبي، 5 على الأقل."
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "CAPTURE_FAILED",
                  "CAPTURE_TIME_BUDGET_EXCEEDED",
                  "CAPTURE_SCREENSHOT_TIMEOUT",
                  "SSRF_BLOCKED",
                  "TARGET_UNREACHABLE",
                  "PAYMENT_REQUIRED",
                  "COMPARE_PROFILE_MISMATCH",
                  "COMPARE_URL_MISMATCH",
                  "RULE902_NOT_AVAILABLE_FOR_MISMATCH",
                  "COMPARE_SOURCE_INTEGRITY_FAILED",
                  "QUOTA_EXCEEDED",
                  "CERTIFY_FAILED",
                  "REDACTION_FAILED"
                ],
                "description": "السبب العلني للفشل (انظر جدول الأخطاء). CAPTURE_SCREENSHOT_TIMEOUT: لم يُنهِ المتصفح لقطة الشاشة؛ CAPTURE_TIME_BUDGET_EXCEEDED: استنفد التشغيل ميزانيته الزمنية كاملة؛ ولا تُحتسب رسوم على أيٍّ منهما. يمكن أن يفشل صف المقارنة (cmp_) أيضًا بالخطأ COMPARE_SOURCE_INTEGRITY_FAILED (حزمة مصدر عُدّلت بعد قبول الطلب)، أو COMPARE_PROFILE_MISMATCH أو COMPARE_URL_MISMATCH (لا يُكتشفان إلا في السجلات المختومة التي يقارنها التشغيل)، أو RULE902_NOT_AVAILABLE_FOR_MISMATCH؛ وتُعاد الوحدة. يفشل توثيق البصمة الذي يُشغَّل في الخلفية بالخطأ QUOTA_EXCEEDED إذا بُلغ الحد الشهري الأقصى عند وقت تشغيله. CERTIFY_FAILED وREDACTION_FAILED: تعذّر إكمال توثيق البصمة أو التنقيح؛ أعد محاولة الطلب."
              },
              "message": {
                "type": "string"
              }
            },
            "description": "failed فقط."
          },
          "failed_at": {
            "type": "string",
            "description": "failed فقط.",
            "format": "date-time"
          },
          "captured_at": {
            "type": "string",
            "description": "وقت التقاط الصفحة (للصفوف المكتملة فقط). في التنقيح، هو وقت التقاط عملية الالتقاط المصدر (ويشير redacted_at إلى وقت إجراء التنقيح).",
            "format": "date-time"
          },
          "redacted_at": {
            "type": "string",
            "format": "date-time",
            "description": "للنسخ المنقّحة فقط: وقت إنشاء النسخة المنقّحة. وقيمة captured_at للنسخة المنقّحة هي وقت التقاط عملية الالتقاط المصدر."
          },
          "compared_at": {
            "type": "string",
            "nullable": true,
            "format": "date-time",
            "description": "للمقارنات فقط: وقت إجراء المقارنة. ويحمل captured_at الخاص بها القيمة نفسها (أُبقي عليه من أجل العملاء الأقدم)؛ استخدم compared_at."
          },
          "package_url": {
            "type": "string",
            "description": "completed فقط: رابط تنزيل موقّع مسبقًا لملف ZIP الخاص بالحزمة، صالح لمدة تصل إلى 24 ساعة (انظر package_url_expires_at)؛ اقرأ عملية الالتقاط مجددًا للحصول على رابط جديد.",
            "format": "uri"
          },
          "package_url_expires_at": {
            "type": "string",
            "description": "completed فقط: وقت توقف package_url عن العمل، بعد 24 ساعة على الأكثر من هذه الاستجابة (قد يتوقف الرابط الموقّع ببيانات اعتماد مؤقتة للخادم في وقت أبكر)؛ اقرأ عملية الالتقاط مجددًا للحصول على رابط جديد.",
            "format": "date-time"
          },
          "verification_url": {
            "type": "string",
            "description": "completed فقط: رابط إلى أداة التحقق العامة من ProofSnap.",
            "format": "uri"
          },
          "manifest_hash": {
            "type": "string",
            "description": "completed فقط: sha256:<hex> للملف manifest.json.",
            "example": "sha256:3f2a..."
          },
          "timestamps": {
            "$ref": "#/components/schemas/Timestamps"
          },
          "c2pa": {
            "type": "object",
            "properties": {
              "included": {
                "type": "boolean"
              },
              "signed_screenshot_sha256": {
                "type": "string",
                "description": "sha256:<hex> للقطة الشاشة الموقّعة.",
                "nullable": true
              }
            },
            "description": "completed فقط."
          },
          "file_count": {
            "type": "integer",
            "description": "completed فقط."
          },
          "size_bytes": {
            "type": "integer",
            "description": "completed فقط."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "enum": [
                    "consent_gated"
                  ]
                },
                "reason": {
                  "type": "string",
                  "nullable": true
                },
                "final_host": {
                  "type": "string",
                  "nullable": true
                },
                "final_url": {
                  "type": "string",
                  "nullable": true
                },
                "message": {
                  "type": "string"
                },
                "remedy": {
                  "type": "object",
                  "properties": {
                    "option": {
                      "type": "string"
                    },
                    "value": {
                      "type": "boolean"
                    }
                  }
                }
              }
            },
            "description": "completed فقط، عندما تنتهي عملية الالتقاط على جدار موافقة على ملفات تعريف الارتباط أو صفحة اعتراضية بدلًا من الصفحة نفسها. أعد التشغيل مع dismiss_consent: true."
          },
          "external_archive": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ExternalArchive"
              }
            ],
            "description": "completed فقط. طلب Internet Archive المُرسل عبر الخيار external_archive: النتيجة الحالية (status)، والحالة المختومة في الحزمة (status_at_sealing)، ورابط النسخة المؤرشفة. { requested: false } عندما لم يُرسل أي طلب."
          },
          "storage": {
            "$ref": "#/components/schemas/CaptureStorage"
          },
          "private_note": {
            "type": "string",
            "nullable": true,
            "description": "ملاحظتك الخاصة (انظر PrivateNote). تُرجَع إلى منشئ عملية الالتقاط فقط؛ ولا تكون أبدًا جزءًا من حزمة الأدلة."
          },
          "legal_hold": {
            "type": "boolean",
            "description": "عملية الالتقاط خاضعة للحفظ القانوني: لا تُحذف ما دامت القيمة true (PATCH /captures/{capture_id} مع action legal_hold). يُرفع تلقائيًا، وتُحذف عملية الالتقاط، عندما ينتهي الاشتراك وتمضي 30 يومًا عليه دون خطة تحتفظ بالبيانات."
          },
          "legal_hold_set_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "وقت فرض الحفظ القانوني الحالي؛ null عند عدم وجود حفظ."
          },
          "legal_hold_reason": {
            "type": "string",
            "nullable": true,
            "description": "السبب المخزَّن مع الحفظ القانوني الحالي؛ null عند عدم وجود حفظ أو سبب."
          },
          "legal_hold_set_by_email": {
            "type": "string",
            "nullable": true,
            "description": "لمالك الشركة فقط، على عملية التقاط تابعة للشركة خاضعة للحفظ: عنوان البريد الإلكتروني لمن فرض الحفظ."
          },
          "legal_hold_source": {
            "type": "string",
            "nullable": true,
            "description": "مصدر الحفظ الحالي: \"manual\" (PATCH /captures/{capture_id})، \"bulk\" (POST /legal-holds)، \"matter:<matter_id>\" (الحفظ القانوني للقضية) أو \"derived:<capture_id>\" (تنقيح أو مقارنة خاضعان للحفظ لأن عملية التقاط أُنشئا منها خاضعة للحفظ). null عند عدم وجود حفظ."
          },
          "legal_hold_released_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "وقت رفع آخر حفظ (بداية مهلة ما بعد الرفع)؛ null ما دام الحفظ قائمًا أو إذا لم يُرفع قط، وكذلك بعد الرفع التلقائي عند انتهاء الاشتراك (دون مهلة)."
          },
          "deletion_blocked_until": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "نهاية المهلة بعد الرفع: حتى ذلك الحين لا تُحذف عملية الالتقاط نهائيًا (DELETE النهائي يُرجع 409 CAPTURE_HOLD_GRACE، ولا إفراغ لسلة المهملات، ولا حذف بموجب مدة الاحتفاظ)؛ ويمكن نقلها إلى سلة المهملات. حذف الحساب والشركة بعد انتهاء الاشتراك لا ينتظرها. null عندما لا تكون هناك مهلة جارية (وكذلك أثناء قيام الحفظ: فهو يمنع الحذف دون نهاية)."
          },
          "s3_legal_hold": {
            "type": "object",
            "nullable": true,
            "description": "الحفظ القانوني S3 Object Lock لكائنات الحزمة المخزّنة (ملف ZIP، وإثبات OpenTimestamps المُحدَّث، والنسخة المكررة المخزّنة بشكل منفصل، وكل إصدار). status: on، off، pending (قيد التطبيق)، failed (يعيد الخادم المحاولة)، unsupported (لا يحتوي التخزين على إعداد Object Lock؛ ويظل الحفظ على سجل عملية الالتقاط ساريًا)، none (لا توجد حزمة مخزّنة، مثل عملية التقاط فاشلة).",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "on",
                  "off",
                  "pending",
                  "failed",
                  "unsupported",
                  "none"
                ]
              },
              "at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          },
          "source_held": {
            "type": "boolean",
            "nullable": true,
            "description": "التنقيحات والمقارنات: true عندما تكون عملية التقاط أُنشئت منها خاضعة للحفظ القانوني. تتبع الحزمة المشتقة حفظ مصدرها: ما دام المصدر خاضعًا للحفظ، يخضع التنقيح أو المقارنة للحفظ أيضًا (legal_hold_source derived:<capture_id>) ولا يمكن رفع حفظه منفردًا (409 LEGAL_HOLD_SOURCE). null لعمليات الالتقاط الأخرى."
          },
          "import": {
            "$ref": "#/components/schemas/CaptureImport"
          },
          "source": {
            "type": "string",
            "description": "طريقة إجراء عملية الالتقاط: api، schedule، certify-hash، compare، redact، import. يُرجعه GET /captures/{capture_id} فقط للحزمة المستوردة (\"import\")؛ أما صفوف GET /captures فتحمله دائمًا."
          }
        }
      },
      "Timestamps": {
        "type": "object",
        "properties": {
          "bitcoin_ots": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "description": "حالة إثبات Bitcoin OpenTimestamps في manifest.json.ots. pending_confirmation: الإثبات بانتظار كتلة Bitcoin (عادةً بضع ساعات؛ تبدأ عمليات الفحص بعد نحو ساعتين من اكتمال عملية الالتقاط وتتكرر بفواصل متزايدة). verified: تشهد كتلة Bitcoin على بصمة البيان (block_height، block_time). not_present: لا تحتوي الحزمة على manifest.json.ots (تعذّر الوصول إلى الخوادم التقويمية عند ختم الحزمة). unconfirmed: لم يُعثر على تأكيد خلال 7 أيام من الفحص؛ ولا يزال بالإمكان تحديث الإثبات الموجود في الحزمة والتحقق منه باستخدام عميل OpenTimestamps (ots upgrade، ots verify). package_missing: لم تعد حزمة الأدلة موجودة في التخزين، لذا لم يعد بالإمكان فحص الإثبات. manifest_mismatch: لا يطابق manifest.json في الحزمة المخزّنة بصمة البيان المسجّلة عند ختمها؛ ولا يُؤكَّد الطابع الزمني له. كلتا الحالتين نهائية. لا تُغيَّر الحزمة نفسها أبدًا. not_checked: اكتملت عملية الالتقاط قبل وجود فحوصات التأكيد التلقائية ولم تُفحص قط؛ وقد تظل حزمتها تحمل manifest.json.ots يمكن لعميل OpenTimestamps تحديثه والتحقق منه. تُرسَل رسائل webhook واستجابات الإنشاء عند الاكتمال، لذا تذكر pending_confirmation (أو not_present عند فشل الخوادم التقويمية)؛ اقرأ GET /captures/{capture_id} لمعرفة الحالة الحالية. تُختم الحزمة والإثبات لا يزال قيد الانتظار: عند تلك اللحظة تكون بصمة البيان قد أُرسلت إلى الخوادم التقويمية لـ OpenTimestamps. بعد التأكيد في كتلة Bitcoin رقم {block_height} ({block_time} UTC)، يُثبت الإثبات أن manifest.json كان موجودًا في موعد لا يتجاوز ذلك الوقت؛ نزّل الإثبات المُحدَّث (upgraded_proof_url) وضعه بجوار manifest.json باسم manifest.json.upgraded.ots، وسيتحقق منه verify.sh وverify.ps1 تلقائيًا.",
                "enum": [
                  "pending_confirmation",
                  "verified",
                  "not_present",
                  "unconfirmed",
                  "package_missing",
                  "manifest_mismatch",
                  "not_checked"
                ]
              },
              "block_height": {
                "type": "integer",
                "nullable": true,
                "description": "ارتفاع كتلة Bitcoin التي تشهد على الإثبات. يُضبط عندما تكون status هي verified."
              },
              "block_time": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "وقت كتلة Bitcoin تلك. في GET /captures/{capture_id} فقط، ويُضبط عندما تكون status هي verified."
              },
              "verified_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "وقت تأكيد ProofSnap للربط. في GET /captures/{capture_id} فقط، ويُضبط عندما تكون status هي verified."
              },
              "upgraded_proof_url": {
                "type": "string",
                "format": "uri",
                "nullable": true,
                "description": "في GET /captures/{capture_id} فقط. بعد التحقق من الربط، يخزّن ProofSnap إثبات OpenTimestamps المُحدَّث من الخوادم التقويمية (يحمل شهادة كتلة Bitcoin، لذا يمكن التحقق منه دون اتصال ودون مراسلة أي خادم تقويمي). هذا رابط تنزيل موقّع مسبقًا له، صالح لمدة تصل إلى 24 ساعة مثل package_url؛ اقرأ عملية الالتقاط مجددًا للحصول على رابط جديد. null عندما لا يوجد (لم يُتحقق منه بعد، أو سيُخزَّن لاحقًا عبر استكمال البيانات في الخلفية). لا تُغيَّر الحزمة المختومة أبدًا: احفظ الملف بجوار manifest.json الخاص بالحزمة باسم manifest.json.upgraded.ots. الحزم المختومة منذ 2026-09-29 تتحقق منه بنصوصها البرمجية المرفقة (يستخدمه verification/verify.sh وverify.ps1 بدلًا من manifest.json.ots)؛ وللحزمة الأقدم شغّل `ots verify manifest.json.upgraded.ots -f manifest.json`. تحمله صادرات القضايا بوصفه captures/<capture_id>.upgraded.ots بجوار captures/<capture_id>.zip. يُحذف مع الحزمة."
              }
            }
          },
          "eidas": {
            "$ref": "#/components/schemas/EidasTimestamp"
          }
        }
      },
      "DiffSummary": {
        "type": "object",
        "properties": {
          "text_insertions": {
            "type": "integer",
            "description": "الأسطر المضافة (القيمة نفسها التي في text_lines_added؛ منذ 2026-10-14 يُحسب النص بالأسطر، انظر text_unit). المقارنات التي أُجريت قبل ذلك تحسب الأحرف. مُهمل (2026-10-14): مُبقى عليه للقرّاء الحاليين، ولم يُحذف؛ استخدم text_lines_added.",
            "deprecated": true
          },
          "text_deletions": {
            "type": "integer",
            "description": "الأسطر المحذوفة (القيمة نفسها التي في text_lines_removed؛ بالأسطر منذ 2026-10-14، وبالأحرف قبل ذلك). مُهمل (2026-10-14): مُبقى عليه للقرّاء الحاليين، ولم يُحذف؛ استخدم text_lines_removed.",
            "deprecated": true
          },
          "text_unit": {
            "type": "string",
            "enum": [
              "line"
            ],
            "description": "وحدة أعداد النص: line (كل سطر من domtextcontent.txt مع محرف نهاية السطر). غائبة في المقارنات التي أُجريت قبل 2026-10-14، والتي كانت تحسب الأحرف."
          },
          "text_lines_added": {
            "type": "integer",
            "description": "الأسطر المضافة (أسطر نص After في المقاطع المضافة من مقارنة الأسطر)."
          },
          "text_lines_removed": {
            "type": "integer",
            "description": "الأسطر المحذوفة (أسطر نص Before في المقاطع المحذوفة من مقارنة الأسطر)."
          },
          "text_lines_before": {
            "type": "integer",
            "description": "أسطر نص Before."
          },
          "text_lines_after": {
            "type": "integer",
            "description": "أسطر نص After."
          },
          "text_changed_percent": {
            "type": "number",
            "description": "100 x (الأسطر المحذوفة + الأسطر المضافة) / (الأسطر قبل + الأسطر بعد)، بمنزلة عشرية واحدة."
          },
          "text_diff_capped": {
            "type": "boolean",
            "description": "true عندما تبلغ مقارنة الأسطر ميزانية عملها ويُحسب جزء من النصوص على أنه مستبدل بالكامل (يبالغ في الإبلاغ، ولا يخفي أي تغيير أبدًا)."
          },
          "metadata_changes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "{ key, before, after, class } لكل حقل في metadata.json يختلف، وهي هذه بالضبط: url، final_url، page_title، http_status، http_headers.content-type، http_headers.server، dns.connected_ip، resolved_ips (يُقارَن كقائمة مرتبة)، tls.issuer، tls.valid_to، وحقول ملف تعريف الجهاز viewport_width وviewport_height وdevice_scale_factor وis_mobile وuser_agent. قيمة class هي content (url، final_url، page_title، http_status، http_headers.content-type: يُحتسب تغييرًا)، أو infrastructure (http_headers.server، dns.connected_ip، resolved_ips، tls.issuer، tls.valid_to: أي خادم سلّم الصفحة، للإبلاغ فقط) أو device_profile (كيف عرضت عملية الالتقاط الصفحة، للإبلاغ فقط). المقارنة التي أُجريت قبل 2026-10-10 لا تحتوي class."
          },
          "meta_content": {
            "type": "integer",
            "description": "حقول البيانات الوصفية المختلفة من الفئة content؛ وتُحتسب ضمن changed."
          },
          "meta_infra": {
            "type": "integer",
            "description": "حقول البيانات الوصفية المختلفة من الفئة infrastructure (برنامج الخادم، وعنوان IP المتصل، وعناوين IP المستخرجة من DNS، ومُصدر شهادة TLS وتاريخ انتهائها). تجيب شبكة توصيل المحتوى من عناوين متغيرة، لذا قد يختلف تحميلان لصفحة لم تتغير هنا؛ يُبلَّغ عنها للعلم، ولا تُحتسب أبدًا ضمن changed."
          },
          "meta_device_profile": {
            "type": "integer",
            "description": "حقول ملف تعريف الجهاز المختلفة (منطقة العرض، والمقياس، ومحاكاة الجوال، ووكيل المستخدم user agent)؛ يُبلَّغ عنها للعلم، ولا تُحتسب أبدًا ضمن changed."
          },
          "image_changes": {
            "type": "object",
            "description": "مقارنة الصور، استنادًا إلى جرد الصور الذي ختمته كل عملية التقاط مصدر (image_inventory.json). قيمة status هي ran، أو not_run مع reason بقيمة source_without_inventory (مصدر التُقط قبل أن تبدأ عمليات الالتقاط في تسجيل الجرد)، أو source_inventory_failed أو source_inventory_unreadable؛ ولا يُستخدم أي شيء آخر بديلًا عنه. النتائج الكاملة لكل صورة موجودة في image_changes.json داخل الحزمة.",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "ran",
                  "not_run"
                ]
              },
              "reason": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "source_without_inventory",
                  "source_inventory_failed",
                  "source_inventory_unreadable",
                  null
                ],
                "description": "سبب عدم مقارنة الصور: source_without_inventory (مصدر أُنشئ قبل أن تبدأ عمليات الالتقاط في تسجيل جرد الصور؛ وعمليات الالتقاط الجديدة تسجّله)، source_inventory_failed (تعذّر تسجيل الجرد عند إجراء عملية الالتقاط تلك) أو source_inventory_unreadable؛ null عندما تُجرى المقارنة."
              },
              "images_changed": {
                "type": "integer",
                "nullable": true,
                "description": "الصور الخاصة بالموقع (المقدَّمة من النطاق القابل للتسجيل الخاص بالصفحة نفسها، أو منذ 2026-10-14 من نطاق تعلنه الصفحة نطاقًا خاصًا بها في og:image أو الأيقونة، أو يقدّم معظم صور الصفحة في كلتا عمليتي الالتقاط: image_changes.json site_image_hosts) التي استُبدلت أو أُضيفت أو حُذفت، وهي جزء الصور من changed؛ ولا تُضمَّن الصور المنقولة. null عندما لم تُجرَ المقارنة. مُهمل (2026-10-14): مُبقى عليه للقرّاء الحاليين، ولم يُحذف؛ استخدم site_images_changed.",
                "deprecated": true
              },
              "first_party": {
                "$ref": "#/components/schemas/ImageChangeCounts"
              },
              "third_party": {
                "$ref": "#/components/schemas/ImageChangeCounts"
              },
              "tracking_pixel": {
                "$ref": "#/components/schemas/ImageChangeCounts"
              },
              "inventories_truncated": {
                "type": "boolean",
                "description": "true عندما يبلغ جرد مصدر حدّ إدخالاته؛ انظر inventory_cutoff."
              },
              "site_images_changed": {
                "type": "integer",
                "description": "مثل images_changed (يُحتسب)."
              },
              "other_images_changed": {
                "type": "integer",
                "description": "الصور من نطاقات أخرى وبكسلات التتبع التي استُبدلت أو حُذفت أو أُضيفت (لا تُحتسب أبدًا)."
              },
              "site_image_hosts": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "النطاقات القابلة للتسجيل التي تُعدّ خاصة بالموقع إلى جانب نطاق الصفحة (site_image_hosts في image_changes.json)."
              },
              "inventory_cutoff": {
                "type": "object",
                "nullable": true,
                "properties": {
                  "before_only_index_limit": {
                    "type": "integer",
                    "nullable": true,
                    "description": "الصورة الموجودة في Before فقط تكون قابلة للحسم (removed) عندما يكون فهرسها في جرد Before أقل من هذه القيمة؛ null: لا حد (جرد After مكتمل)."
                  },
                  "after_only_index_limit": {
                    "type": "integer",
                    "nullable": true,
                    "description": "الصورة الموجودة في After فقط تكون قابلة للحسم (added) عندما يكون فهرسها في جرد After أقل من هذه القيمة؛ null: لا حد (جرد Before مكتمل)."
                  }
                },
                "description": "منذ التدقيق الثاني بتاريخ 2026-10-14 (الخوارزمية 3 في image_changes.json): كائن، ويُحسم بحسب فهرس المستند: عندما يبلغ الجرد الآخر حدّ إدخالاته، لا تكون الصورة الموجودة في جانب واحد فقط قابلة للحسم إلا إذا كان فهرسها أقل من عدد إدخالات الجرد الآخر مطروحًا منه هامش (الأكبر بين 10 إدخالات و5 بالمئة من ذلك العدد)؛ وإلا تكون غير محسومة (undetermined_reason beyond_inventory_limit). null عندما لا يكون أي جرد مقتطعًا. المقارنات التي أُجريت قبل ذلك تحمل رقمًا (الموضع النسبي في المستند وفق القاعدة السابقة)."
              }
            }
          },
          "changed": {
            "type": "boolean",
            "nullable": true,
            "description": "true عندما يختلف النص المرئي، أو يختلف حقل بيانات وصفية للمحتوى (meta_content)، أو تُستبدل صورة خاصة بالموقع أو تُحذف أو تُضاف (image_changes.images_changed). حقول البنية التحتية وملف تعريف الجهاز، والصور المنقولة (البايتات نفسها في عنوان آخر)، وصور الجهات الخارجية، وبكسلات التتبع يُبلَّغ عنها لكنها لا تضبط هذا الحقل. null (منذ 2026-10-14) عندما لا يتغير أي شيء محتسب على نحو موثوق لكن نتيجة النص أو الصور غير محسومة (عُرضت عمليات الالتقاط بشكل مختلف، allow_profile_mismatch). تذكر الحزمة القاعدة في comparison_method.json counted_as_change. مُهمل (2026-10-14): مُبقى عليه للقرّاء الحاليين، ولم يُحذف؛ استخدم verdict (changed | unchanged | undetermined) وtext_verdict وimages_verdict وmetadata_verdict لكل نوع.",
            "deprecated": true
          },
          "text_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined"
            ],
            "description": "undetermined عندما تُعرض عمليتا الالتقاط بملف تعريف جهاز أو إعدادات التقاط مختلفة (allow_profile_mismatch)."
          },
          "images_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined",
              "not_run"
            ]
          },
          "metadata_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged"
            ],
            "description": "من حقول البيانات الوصفية من الفئة content (العنوان، والعنوان النهائي، والعنوان الرئيسي للصفحة، وحالة HTTP، ونوع المحتوى)."
          },
          "verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined"
            ],
            "description": "النتيجة الإجمالية بالكلمات: changed عندما تكون changed هي true، وundetermined عندما تكون changed هي null."
          },
          "undetermined_reason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "render_profile_mismatch",
              "capture_settings_mismatch",
              null
            ]
          },
          "text_changes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "GET فقط: حتى 60 مقطعًا نصيًا متغيرًا."
          },
          "change_regions": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "GET فقط: حتى 60 تغييرًا نصيًا محدد الموقع على لقطات الشاشة (من text_layout.json الخاص بالمصادر)، ببكسلات لقطة الشاشة. يحمل كل تغيير counterpart_y_px (موضعه العمودي على لقطة الشاشة الأخرى، منقولًا من أقرب كتلة نصية لم تتغير) وreport_region (رقم موضع التغيير الذي يعرضه فيه evidence.pdf، 10 مواضع على الأكثر، أو null) كما في change_regions.json."
          },
          "change_regions_info": {
            "type": "object",
            "nullable": true
          },
          "order_reversed": {
            "type": "boolean",
            "description": "GET فقط: true عندما تكون عملية الالتقاط المعطاة بوصفها capture_id_1 (Before) قد التُقطت بعد تلك المعطاة بوصفها capture_id_2 (After)، كما في compare.json order_reversed. تحتفظ المقارنة بالترتيب المعطى؛ ويذكر التقرير ولوحة التحكم ذلك."
          },
          "render_profile_mismatch": {
            "type": "boolean",
            "description": "GET فقط: عُرضت عمليتا الالتقاط بملف تعريف جهاز مختلف (compare.json)."
          },
          "capture_settings_mismatch": {
            "type": "boolean",
            "description": "GET فقط: أُجريت عمليتا الالتقاط بإعدادات التقاط مختلفة (compare.json)."
          },
          "capture_settings_differing": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "setting": {
                  "type": "string"
                },
                "before": {},
                "after": {}
              }
            },
            "description": "GET فقط: الإعدادات المختلفة، { setting, before, after }."
          },
          "profile_mismatch_allowed": {
            "type": "boolean",
            "description": "GET فقط: أُجريت المقارنة مع allow_profile_mismatch لعمليات التقاط عُرضت بشكل مختلف. مُهمل (2026-10-14): مُبقى عليه للقرّاء الحاليين، ولم يُحذف؛ استخدم render_profile_mismatch وcapture_settings_mismatch.",
            "deprecated": true
          },
          "url_mismatch": {
            "type": "boolean",
            "description": "GET فقط: لا تشترك عمليتا الالتقاط في أي عنوان (أُجريت مع allow_url_mismatch)."
          }
        },
        "description": "ملخص المقارنة."
      },
      "ImageChangeCounts": {
        "type": "object",
        "properties": {
          "unchanged": {
            "type": "integer"
          },
          "replaced": {
            "type": "integer"
          },
          "moved": {
            "type": "integer"
          },
          "removed": {
            "type": "integer"
          },
          "added": {
            "type": "integer"
          },
          "undetermined": {
            "type": "integer"
          },
          "alt_changed": {
            "type": "integer"
          },
          "position_changed": {
            "type": "integer"
          },
          "undetermined_same_error": {
            "type": "integer",
            "description": "الأزواج غير المحسومة التي أرجع جانباها حالة خطأ HTTP نفسها (مثل 404 في كلتا عمليتي الالتقاط)؛ مُضمَّنة في undetermined، وتُحتسب في التقرير ولا تُدرج."
          },
          "occurrences": {
            "type": "integer",
            "description": "جميع نتائج هذا الطرف: مرات الظهور في الصفحة."
          },
          "distinct": {
            "type": "object",
            "description": "عدد الصور المختلفة بين النتائج، إجمالًا ولكل فئة (الصورة المعروضة عدة مرات تُحتسب مرة واحدة): تمثّل كل نتيجة بصمة SHA-256 لجانب After منها، أو لجانب Before إن لم يكن لها جانب After، أو عنوان ذلك الجانب إن لم يحمل SHA-256.",
            "properties": {
              "total": {
                "type": "integer"
              },
              "unchanged": {
                "type": "integer"
              },
              "replaced": {
                "type": "integer"
              },
              "moved": {
                "type": "integer"
              },
              "removed": {
                "type": "integer"
              },
              "added": {
                "type": "integer"
              },
              "undetermined": {
                "type": "integer"
              }
            }
          }
        },
        "description": "الأعداد حسب الفئة، محسوبةً بعدد مرات الظهور في الصفحة (الصورة المعروضة ثلاث مرات تُحسب ثلاث مرات): unchanged، replaced (العنوان نفسه، بايتات مختلفة)، moved (عنوان مختلف، البايتات نفسها؛ يُبلَّغ عنها ولا تُحسب تغييرًا)، removed، added، undetermined (صورة تفتقر إلى SHA-256 في أحد الجانبين، ولا تُحسب أبدًا محذوفة أو مضافة)؛ إضافةً إلى الصور المقترنة التي تغيّر نصها البديل (alt) أو موضعها بين صور الصفحة، والأزواج غير المحددة التي تحمل خطأ HTTP نفسه في الجانبين، وعدد الصور المتمايزة."
      },
      "BatchRequest": {
        "type": "object",
        "required": [
          "urls"
        ],
        "properties": {
          "urls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri",
              "maxLength": 2048
            },
            "minItems": 2,
            "maxItems": 50,
            "description": "من 2 إلى 50 عنوان URL، يُتحقق من كل منها كما يُتحقق من CaptureRequest.url. لا يُتحقق من أسماء المضيفين عبر DNS عند قبول الدفعة؛ وأي عنوان URL يفشل في فحص DNS أو فحص العنوان يفشل لاحقًا بالحالة failed (مع الحدث capture.failed)."
          },
          "options": {
            "$ref": "#/components/schemas/CaptureOptions"
          },
          "private_note": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PrivateNote"
              }
            ],
            "description": "ملاحظة خاصة واحدة تُخزَّن مع كل عملية التقاط في الدفعة. ملاحظتك الخاصة على عملية الالتقاط (مثل «التقاط من الجوال»)، بحد أقصى 200 حرف، نص عادي (قاعدة PlainText). تُخزَّن في سجل عملية الالتقاط فقط: فهي ليست جزءًا من حزمة الأدلة المختومة (لا في ملف ZIP ولا في PDF ولا في البيان ولا في أي بصمة) وليس لها أي قيمة إثباتية، لذا يمكن تغييرها أو مسحها لاحقًا عبر PATCH /v1/captures/{capture_id} دون التأثير في الأدلة. لا يقرؤها إلا منشئ عملية الالتقاط. لعدم إضافة ملاحظة، احذف الحقل أو أرسل null أو سلسلة فارغة."
          }
        }
      },
      "BatchResult": {
        "type": "object",
        "properties": {
          "batch_id": {
            "type": "string",
            "example": "batch_a1B2c3D4e5F6g7H8"
          },
          "count": {
            "type": "integer"
          },
          "captures": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "capture_id": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "description": "failed عندما يتعذر بدء عملية الالتقاط.",
                  "enum": [
                    "processing",
                    "failed"
                  ]
                }
              }
            }
          },
          "not_admitted": {
            "type": "array",
            "description": "فقط عند بلوغ حد عمليات الالتقاط الجارية في آنٍ واحد للحساب: عناوين URL التي لم تبدأ، بترتيب إرسالها. لا تُكلّف شيئًا (لا تستهلك حد المعدّل ولا وحدة شهرية)؛ أرسلها مجددًا لاحقًا. لا يظهر هذا الحقل عندما تبدأ جميع عناوين URL.",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string"
                },
                "code": {
                  "type": "string",
                  "description": "سبب عدم بدء عنوان URL.",
                  "enum": [
                    "CONCURRENT_CAPTURE_LIMIT"
                  ]
                }
              }
            }
          },
          "in_flight_limit": {
            "type": "integer",
            "description": "فقط مع not_admitted: عدد عمليات الالتقاط التي يمكن للحساب تشغيلها في الوقت نفسه (developer 10، startup 25، growth 100، legal_solo و legal_seats 25، custom 200، website_archive 25، sandbox 5)."
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "VerifyResult": {
        "type": "object",
        "properties": {
          "verdict": {
            "type": "string",
            "nullable": true,
            "description": "match: التوقيع صالح وكل ملف مُدرج موجود ببصمة SHA-256 المسجلة له. modified: التوقيع غير صالح أو ملف مفقود أو مُعدَّل. invalid: تعذّر فحص الحزمة. null فقط لحزمة مستوردة لم يُعَد فحصها هذه المرة (`rechecked: false`، مع `reason` والتحقق المسجَّل عند الاستيراد في `stored_verification`).",
            "enum": [
              "match",
              "modified",
              "invalid",
              null
            ]
          },
          "reason": {
            "type": "string",
            "description": "invalid فقط؛ ولحزمة مستوردة بقيمة `rechecked: false`: سبب عدم إعادة فحصها (too_large_for_synchronous_check، time_budget، storage_error)."
          },
          "capture_id": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "s3"
            ]
          },
          "checks": {
            "type": "object",
            "properties": {
              "manifest_signature": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "valid",
                      "invalid"
                    ]
                  },
                  "algorithm": {
                    "type": "string",
                    "enum": [
                      "RSA-4096"
                    ]
                  }
                }
              },
              "declared_files": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "all_match",
                      "mismatch"
                    ]
                  },
                  "mismatched": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "missing": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "bitcoin_ots": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "وجود manifest.json.ots فقط؛ لا يُفحص الإثبات نفسه هنا.",
                    "enum": [
                      "present",
                      "not_present"
                    ]
                  }
                }
              },
              "eidas_disig": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "وجود manifest.json.tsr. يُفحص الرمز المميز (token) نفسه في checks.eidas. missing: يسجّل manifest.json القيمة eidas_requested (صدرت الحزمة مع الرمز المميز)، لكن manifest.json.tsr غير موجود في الحزمة.",
                    "enum": [
                      "present",
                      "missing"
                    ]
                  }
                },
                "nullable": true,
                "description": "null عندما لا يوجد manifest.json.tsr ولا يسجّل manifest.json القيمة eidas_requested."
              },
              "proofsnap_issuance": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "issued_by_proofsnap: بصمة البيان موجودة في السجل العام للحزم الصادرة من ProofSnap. unverified: ليست موجودة فيه (الحزم الصادرة قبل إنشاء السجل العام غير مدرجة فيه). unknown: فشل البحث.",
                    "enum": [
                      "issued_by_proofsnap",
                      "unverified",
                      "unknown"
                    ]
                  },
                  "manifest_sha256": {
                    "type": "string"
                  },
                  "issued_at": {
                    "type": "string",
                    "nullable": true,
                    "format": "date-time"
                  },
                  "type": {
                    "type": "string"
                  }
                }
              },
              "eidas": {
                "type": "object",
                "nullable": true,
                "description": "null عندما لا يوجد manifest.json.tsr ولا يسجّل manifest.json القيمة eidas_requested. رمز الطابع الزمني eIDAS كما فحصه ProofSnap، عند الطلب، من الحزمة المخزنة. هذا فحص يجريه ProofSnap؛ ويوضح الحقل note كيفية التحقق من الرمز المميز بشكل مستقل.",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "verified",
                      "verified_revocation_unknown",
                      "failed",
                      "unavailable",
                      "missing"
                    ],
                    "description": "missing: يسجّل manifest.json القيمة eidas_requested: true، أي أن الحزمة صدرت مع طابع زمني مؤهل، لكن manifest.json.tsr غير موجود فيها (وعندئذ تتضمن الاستجابة التحذير EIDAS_TOKEN_MISSING). verified: نجحت جميع الفحوص، وحالة شهادة سلطة الطوابع الزمنية (TSA) المسجلة عند الإصدار معروفة (good، أو revoked بعد وقت الرمز المميز لسبب غير اختراق المفتاح). verified_revocation_unknown: نجحت جميع الفحوص التي أُجريت، لكن تعذّر تحديد حالة شهادة TSA من البيانات المسجلة (revocation.status بقيمة unknown أو not_checked). failed: فشل فحص واحد على الأقل، انظر failures (وعندئذ تتضمن الاستجابة التحذير EIDAS_TOKEN_FAILED). unavailable: لا يوجد حكم بشأن الرمز المميز (reason: not configured، timeout، input_rejected عندما يرفض المدقق المُدخل مثل بيانات تحقق كبيرة الحجم، inconclusive)؛ ولا يُستنتج من ذلك شيء عن الرمز المميز."
                  },
                  "reason": {
                    "type": "string",
                    "description": "unavailable فقط: timeout أو input_rejected أو inconclusive أو شرح موجز."
                  },
                  "checked_by": {
                    "type": "string",
                    "example": "ProofSnap API on request"
                  },
                  "checked_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "gen_time": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true,
                    "description": "الوقت المسجل في الرمز المميز (TSTInfo genTime)."
                  },
                  "tsa_name": {
                    "type": "string",
                    "nullable": true,
                    "description": "مأخوذ من شهادة الموقِّع (الاسم الشائع والمؤسسة)، ولا يكون أبدًا قيمة افتراضية."
                  },
                  "serial_number": {
                    "type": "string",
                    "nullable": true
                  },
                  "policy_oid": {
                    "type": "string",
                    "nullable": true
                  },
                  "hash_algorithm": {
                    "type": "string",
                    "nullable": true
                  },
                  "signature_algorithm": {
                    "type": "string"
                  },
                  "signing_certificate_form": {
                    "type": "string",
                    "enum": [
                      "ESSCertIDv2",
                      "ESSCertID"
                    ]
                  },
                  "signer_certificate": {
                    "type": "object",
                    "nullable": true,
                    "properties": {
                      "subject": {
                        "type": "string"
                      },
                      "issuer": {
                        "type": "string"
                      },
                      "serial_number": {
                        "type": "string"
                      },
                      "sha256": {
                        "type": "string"
                      },
                      "not_before": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "not_after": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  },
                  "root": {
                    "type": "object",
                    "properties": {
                      "label": {
                        "type": "string",
                        "example": "CA Disig QCA4"
                      },
                      "sha256": {
                        "type": "string"
                      }
                    }
                  },
                  "chain_checked_at": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true,
                    "description": "الوقت الذي فُحصت السلسلة بالنسبة إليه: genTime الخاص بالرمز المميز، لذا فإن الشهادة التي انتهت صلاحيتها بعد إصدار الرمز المميز لا تُفشله."
                  },
                  "chain": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "subject": {
                          "type": "string"
                        },
                        "sha256": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "revocation": {
                    "type": "object",
                    "description": "حالة شهادة TSA من البيانات المسجلة عند الإصدار (basis recorded_at_issuance)، يُتحقق منها بالتوقيع ويُشترط أن تكون سارية عند genTime. لا تُجلب مباشرة في الوقت الفعلي.",
                    "properties": {
                      "status": {
                        "type": "string",
                        "enum": [
                          "good",
                          "revoked",
                          "revoked_after_gen_time",
                          "unknown",
                          "not_checked"
                        ],
                        "description": "revoked_after_gen_time: أُلغيت بعد وقت الرمز المميز لسبب غير اختراق المفتاح (يبقى الرمز المميز متحققًا منه). not_checked: لم تُسجَّل بيانات إلغاء ولم تُفحص."
                      },
                      "source": {
                        "type": "string",
                        "enum": [
                          "ocsp",
                          "crl"
                        ],
                        "nullable": true
                      },
                      "basis": {
                        "type": "string",
                        "enum": [
                          "recorded_at_issuance"
                        ]
                      },
                      "produced_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "this_update": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "next_update": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                      },
                      "reason": {
                        "type": "string",
                        "description": "سبب كون الحالة غير معروفة."
                      }
                    }
                  },
                  "checks": {
                    "type": "object",
                    "description": "نتيجة كل فحص: passed، failed، not_applicable (nonce: لا توجد قيمة مسجلة لرمز مميز مخزن)، unknown (حالة الإلغاء دون بيانات قابلة للاستخدام) أو not_checked (أوقف إخفاقٌ سابق الفحص).",
                    "properties": {
                      "response_status": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "message_imprint": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "nonce": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "content_type": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "message_digest": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "signature": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "signing_certificate": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "timestamping_eku": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "chain": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "revocation": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      }
                    }
                  },
                  "failures": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "check": {
                          "type": "string"
                        },
                        "reason": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "note": {
                    "type": "string"
                  },
                  "detail": {
                    "type": "string",
                    "nullable": true,
                    "description": "input_rejected فقط: ما رفضه المدقق."
                  }
                }
              }
            },
            "description": "غير موجود عندما تكون قيمة verdict هي invalid. يعتمد verdict فقط على manifest_signature و declared_files؛ ويُبلَّغ عن eidas بجانبه، وعند إخفاقه يُبلَّغ عنه أيضًا في warnings."
          },
          "warnings": {
            "type": "array",
            "description": "يظهر فقط عندما يتطلب أمرٌ خارج الحكم الانتباه. EIDAS_TOKEN_FAILED: لم يجتز manifest.json.tsr الفحص في checks.eidas. EIDAS_TOKEN_MISSING: يسجّل manifest.json القيمة eidas_requested، لكن manifest.json.tsr غير موجود في الحزمة.",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "enum": [
                    "EIDAS_TOKEN_FAILED",
                    "EIDAS_TOKEN_MISSING"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ScheduleRequest": {
        "type": "object",
        "required": [
          "url",
          "mode"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "الصفحة المراد التقاطها، يُتحقق منها كما يُتحقق من CaptureRequest.url.",
            "format": "uri",
            "maxLength": 2048
          },
          "mode": {
            "type": "string",
            "description": "interval: كل interval_seconds. cron: وفق cron_expr. on_change: يفحص الصفحة كل interval_seconds ولا يلتقطها إلا عندما يتغير نصها.",
            "enum": [
              "interval",
              "cron",
              "on_change"
            ]
          },
          "interval_seconds": {
            "type": "integer",
            "description": "مطلوب لـ interval و on_change. لا يقل عن الحد الأدنى للخطة (300 في developer، startup، growth، custom، legal_solo و legal_seats؛ 86400 في website_archive)، وإلا يُعاد 400 INTERVAL_TOO_SHORT.",
            "minimum": 300
          },
          "cron_expr": {
            "type": "string",
            "description": "مطلوب لـ cron (بحد أقصى 200 حرف). يُعيد التعبير غير القابل للتحليل 400 INVALID_CRON؛ ويُعيد تشغيلان متتاليان أقرب من الحد الأدنى للخطة 400 INTERVAL_TOO_SHORT.",
            "maxLength": 200,
            "example": "0 9 * * 1-5"
          },
          "timezone": {
            "type": "string",
            "description": "المنطقة الزمنية وفق IANA المستخدمة لتقييم cron_expr. القيمة الافتراضية UTC.",
            "example": "Europe/Berlin"
          },
          "webhook_url": {
            "type": "string",
            "description": "عنوان URL بـ https. ترسل عمليات تشغيل interval و cron الحدث capture.completed أو capture.failed؛ ويرسل on_change الحدث monitor.change_detected. يجب أن يستخدم عنوان URL بروتوكول https وألا يشير إلى عنوان خاص أو loopback أو link-local أو carrier-grade NAT أو عنوان بيانات وصفية سحابية (القواعد نفسها المطبقة على عنوان URL لعملية الالتقاط؛ وإلا يُعاد 422 SSRF_BLOCKED). القيمة الفارغة أو المكوّنة من مسافات فقط تعني عدم وجود webhook. للجدول webhook_url واحد في المستوى الأعلى: لا يُخزَّن options.webhook_url (وعندما لا يُرسَل webhook_url في المستوى الأعلى يُستخدم بدلًا منه)، لذا لا يرسل جدول on_change أبدًا الحدث capture.completed إضافةً إلى ذلك. يتطلب مفتاح API: إذا أُرسل من جلسة في لوحة التحكم يُرفض بالخطأ 400 WEBHOOK_REQUIRES_API_KEY، لأن رسائل webhook تُوقَّع بسر webhook الخاص بالمفتاح.",
            "format": "uri"
          },
          "js_render": {
            "type": "boolean",
            "description": "on_change فقط: يفحص الصفحة بعرض كامل في المتصفح بدلًا من جلب HTTP عادي (للصفحات التي تحتاج إلى JavaScript).",
            "default": false
          },
          "options": {
            "$ref": "#/components/schemas/CaptureOptions"
          },
          "max_capture_age_seconds": {
            "type": "integer",
            "nullable": true,
            "minimum": 86400,
            "maximum": 34560000,
            "description": "on_change فقط: يُجري أيضًا عملية التقاط كاملة (ترسل capture.completed) عندما تكون أحدث عملية التقاط للجدول أقدم من هذه المدة، حتى لو لم تتغير الصفحة. للقواعد التي تشترط نسخة مرة كل N يومًا على الأقل، مثل 7776000 لمدة 90 يومًا. من 1 إلى 400 يوم، وأطول من interval_seconds؛ وتعني null (الافتراضية) الالتقاط عند التغيير فقط. يزيله PATCH بالقيمة null."
          },
          "alert_on": {
            "type": "string",
            "enum": [
              "any",
              "text"
            ],
            "nullable": true,
            "description": "on_change فقط (2026-10-14): أي تغيير مكتشف يرسل monitor.change_detected. any (الافتراضي، السلوك السابق): كل عملية التقاط ناتجة عن تغيير. text: فقط عندما تجد المقارنة مع عملية الالتقاط السابقة أن النص المرئي تغيّر (diff_summary.text_verdict بقيمة changed)؛ أما عملية الالتقاط الناتجة عن تغيير التي تفيد مقارنتها بأن النص لم يتغير أو تعذّر تحديد ذلك فلا ترسل شيئًا (ومع ذلك تُجرى عملية الالتقاط وتُحتسب في الفوترة وترسل capture.completed إلى الاشتراكات). في غياب نتيجة مقارنة (لا توجد عملية التقاط مرجعية، أو فشلت المقارنة) يُرسَل التنبيه. يُعاد 400 MALFORMED_REQUEST مع أي وضع أو قيمة أخرى. null لجداول interval و cron."
          }
        },
        "example": {
          "url": "https://example.com/terms",
          "mode": "on_change",
          "interval_seconds": 3600,
          "webhook_url": "https://your-app.example/webhooks/proofsnap"
        }
      },
      "Schedule": {
        "type": "object",
        "properties": {
          "schedule_id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "interval",
              "cron",
              "on_change"
            ]
          },
          "interval_seconds": {
            "type": "integer",
            "nullable": true
          },
          "cron_expr": {
            "type": "string",
            "nullable": true
          },
          "timezone": {
            "type": "string",
            "nullable": true
          },
          "webhook_url": {
            "type": "string",
            "nullable": true,
            "description": "webhook الجدول (للجداول الأقدم التي احتفظت به في options.webhook_url، تُعرض تلك القيمة هنا ولا يحتويها options أبدًا). يتطلب مفتاح API: إذا أُرسل من جلسة في لوحة التحكم يُرفض بالخطأ 400 WEBHOOK_REQUIRES_API_KEY، لأن رسائل webhook تُوقَّع بسر webhook الخاص بالمفتاح."
          },
          "options": {
            "type": "object",
            "description": "خيارات عملية الالتقاط بعد تطبيق القيم الافتراضية."
          },
          "js_render": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused"
            ]
          },
          "next_run_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_run_at": {
            "type": "string",
            "nullable": true,
            "format": "date-time"
          },
          "last_capture_id": {
            "type": "string",
            "nullable": true
          },
          "run_count": {
            "type": "integer"
          },
          "consecutive_failures": {
            "type": "integer"
          },
          "pause_reason": {
            "type": "string",
            "description": "سبب الإيقاف المؤقت التلقائي للجدول: max_failures (5 عمليات تشغيل فاشلة متتالية)، no_billing، plan_downgrade (حدود الخطة، أو خطة Archive only)، invalid_cron أو key_revoked. null عندما يكون نشطًا أو أوقفته أنت مؤقتًا.",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "max_capture_age_seconds": {
            "type": "integer",
            "nullable": true
          },
          "current_capture_id": {
            "type": "string",
            "nullable": true,
            "description": "أحدث عملية التقاط بالحالة COMPLETED للجدول (نسختها الحالية؛ last_capture_id هو أحدث عملية التقاط أُطلقت)."
          },
          "current_captured_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "alert_on": {
            "type": "string",
            "enum": [
              "any",
              "text"
            ],
            "nullable": true,
            "description": "on_change: any أو text (any للجدول المُنشأ قبل إضافة هذا الخيار)؛ null لـ interval و cron."
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "Matter": {
        "type": "object",
        "properties": {
          "matter_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "client": {
            "type": "string",
            "nullable": true
          },
          "case_number": {
            "type": "string",
            "nullable": true
          },
          "opposing_party": {
            "type": "string",
            "nullable": true
          },
          "jurisdiction": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "archived",
              "sealed"
            ]
          },
          "sealed": {
            "type": "boolean"
          },
          "capture_count": {
            "type": "integer"
          },
          "custody_count": {
            "type": "integer",
            "description": "الإدخالات في سجل الحيازة."
          },
          "custody_head_hash": {
            "type": "string",
            "description": "sha256:<hex> لأحدث إدخال في سجل الحيازة.",
            "nullable": true
          },
          "seal": {
            "type": "object",
            "nullable": true,
            "description": "null حتى الختم.",
            "allOf": [
              {
                "$ref": "#/components/schemas/MatterSeal"
              }
            ]
          },
          "member_uids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "created_by": {
            "type": "string",
            "description": "المالك.",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "read_only": {
            "type": "boolean",
            "description": "يظهر (true) في قضية تابعة لشركة انتهى اشتراكها أو تُحفظ كأرشيف للشركة: يُسمح فقط بالفتح والتصدير وإلغاء رابط المشاركة."
          },
          "legal_hold": {
            "type": "boolean",
            "description": "الحفظ القانوني للقضية مُفعَّل (PATCH { legal_hold })."
          },
          "legal_hold_set_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "MatterSeal": {
        "type": "object",
        "properties": {
          "root_hash": {
            "type": "string",
            "description": "sha256:<hex>: رأس سلسلة الحيازة الذي خُتم وأُرسل إلى OpenTimestamps."
          },
          "ots": {
            "type": "string",
            "description": "إثبات OpenTimestamps، بصيغة hex."
          },
          "ots_pending": {
            "type": "boolean"
          },
          "eidas": {
            "type": "object",
            "properties": {
              "tsr_base64": {
                "type": "string",
                "nullable": true
              },
              "serial": {
                "type": "string",
                "nullable": true
              },
              "issued_at": {
                "type": "string",
                "nullable": true
              },
              "ltv": {
                "type": "boolean"
              },
              "verification_status": {
                "type": "string",
                "enum": [
                  "verified"
                ],
                "description": "اجتاز الرمز المميز فحص ProofSnap عند الختم (مدقق مشترك، والفحوص نفسها المطبقة على الرمز المميز لعملية الالتقاط)."
              },
              "revocation_status": {
                "type": "string",
                "enum": [
                  "good",
                  "revoked_after_gen_time",
                  "unknown",
                  "not_checked"
                ],
                "description": "حالة شهادة TSA المحددة عند الختم من استجابة OCSP أو قائمة CRL التي جُمعت حينها."
              },
              "validation": {
                "type": "object",
                "nullable": true,
                "description": "محتوى eidas_validation.json المسجل عند الختم، لفحص الرمز المميز دون اتصال: tsaCertChain، rootCaPem، ocspResponse، crlResponse (يُحذف ويُذكر في omitted عندما يكون كبيرًا جدًا)، ocspUrl، ocspFetchedAt، ocspStatus، crlUrl، verification. لا تحتوي عمليات الختم التي أُجريت قبل 2026-10-01 على شيء منه.",
                "additionalProperties": true
              }
            },
            "nullable": true,
            "description": "طابع زمني eIDAS على البصمة نفسها عندما يكون eIDAS مُهيأً في النشر، ويكون المستدعي في الوضع الفعلي، ويُحصل على رمز مميز يجتاز الفحص، وإلا null (انظر eidas_status)."
          },
          "sealed_by": {
            "type": "string",
            "nullable": true
          },
          "sealed_at": {
            "type": "string",
            "format": "date-time"
          },
          "eidas_status": {
            "type": "string",
            "enum": [
              "confirmed",
              "not_obtained",
              "not_configured"
            ],
            "description": "غير موجود في عمليات الختم التي أُجريت قبل 2026-10-01. not_obtained: لا يحمل الختم إلا تثبيت OpenTimestamps (eidas_reason بقيمة token_refused أو unavailable أو test_key). انتهاء مهلة طابع eIDAS لا يُنتج ختمًا أبدًا: بل يُعيد الطلب 503 EIDAS_SEAL_RETRY بدلًا من ذلك."
          },
          "eidas_reason": {
            "type": "string",
            "enum": [
              "token_refused",
              "unavailable",
              "test_key",
              "account_limit"
            ],
            "description": "not_obtained فقط. test_key: خُتم بواسطة مستدعٍ في وضع الاختبار (مفتاح اختبار لـ API أو جلسة في وضع الاختبار (sandbox))، لذا لم يُطلب طابع زمني eIDAS. account_limit: ليس للحساب فاتورة مدفوعة بعد، وقد بلغ حد eIDAS (20 في اليوم بتوقيت UTC، و 50 في الشهر بتوقيت UTC)."
          }
        },
        "nullable": true
      },
      "CustodyEntry": {
        "type": "object",
        "properties": {
          "seq": {
            "type": "integer"
          },
          "ts": {
            "type": "string",
            "format": "date-time"
          },
          "actor_uid": {
            "type": "string",
            "nullable": true
          },
          "actor_name": {
            "type": "string",
            "nullable": true
          },
          "actor_role": {
            "type": "string",
            "enum": [
              "api",
              "user"
            ],
            "nullable": true
          },
          "action": {
            "type": "string",
            "description": "على سبيل المثال matter.created، matter.updated، capture.added، capture.removed، capture.legal_hold_placed، capture.legal_hold_released، member.added، member.removed، package.imported (حزمة أدلة مستوردة مع matter_id؛ و target هو معرّف عملية الالتقاط)."
          },
          "target": {
            "type": "string",
            "nullable": true
          },
          "prev_hash": {
            "type": "string"
          },
          "entry_hash": {
            "type": "string"
          },
          "details": {
            "type": "object",
            "description": "package.imported فقط: manifest_sha256، zip_sha256، package_kind، verdict، levels. يُخزَّن مع الإدخال لكنه لا يدخل إطلاقًا في entry_hash.",
            "additionalProperties": true
          }
        },
        "description": "حلقة واحدة من سلسلة البصمات. يشمل entry_hash الحقول seq، ts، actor، action، target و prev_hash."
      },
      "MatterCapture": {
        "type": "object",
        "properties": {
          "capture_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "source": {
            "type": "string"
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string"
          },
          "captured_at": {
            "type": "string",
            "nullable": true
          },
          "file_count": {
            "type": "integer",
            "nullable": true
          },
          "manifest_hash": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "AuditEntry": {
        "type": "object",
        "properties": {
          "ts": {
            "type": "string"
          },
          "actor_uid": {
            "type": "string",
            "nullable": true
          },
          "actor_name": {
            "type": "string",
            "nullable": true
          },
          "actor_role": {
            "type": "string",
            "nullable": true
          },
          "action": {
            "type": "string",
            "description": "على سبيل المثال capture.downloaded، export.generated، share.created، share.revoked."
          },
          "target": {
            "type": "string",
            "nullable": true
          },
          "source_ip": {
            "type": "string",
            "nullable": true,
            "description": "لمالك القضية فقط (غير موجود للأعضاء)."
          },
          "user_agent": {
            "type": "string",
            "nullable": true,
            "description": "لمالك القضية فقط (غير موجود للأعضاء)."
          }
        },
        "description": "حدث وصول. ليس جزءًا من سلسلة البصمات."
      },
      "CertifyResult": {
        "type": "object",
        "properties": {
          "certify_id": {
            "type": "string",
            "example": "cert_a1B2c3D4e5F6g7H8"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ]
          },
          "hash_sha256": {
            "type": "string",
            "description": "للشهادة المفردة فقط."
          },
          "combined": {
            "type": "boolean",
            "description": "للشهادة المجمّعة فقط."
          },
          "file_count": {
            "type": "integer",
            "description": "للشهادة المجمّعة فقط."
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "التسمية التي أرسلتها.",
                  "nullable": true
                },
                "sha256": {
                  "type": "string"
                }
              }
            },
            "description": "للشهادة المجمّعة فقط."
          },
          "label": {
            "type": "string",
            "description": "تسميتك، أو «N files» للشهادة المجمّعة.",
            "nullable": true
          },
          "certified_at": {
            "type": "string",
            "format": "date-time"
          },
          "timestamps": {
            "type": "object",
            "properties": {
              "bitcoin_ots": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "pending_confirmation",
                      "not_present"
                    ],
                    "description": "pending_confirmation: يوجد manifest.json.ots في الحزمة وينتظر كتلة Bitcoin. not_present: تعذّر الوصول إلى خوادم تقويم OpenTimestamps، ولا تحتوي الحزمة على manifest.json.ots ولا يُدرجه manifest.json."
                  },
                  "block_height": {
                    "type": "integer",
                    "nullable": true
                  }
                }
              },
              "eidas": {
                "$ref": "#/components/schemas/EidasTimestamp"
              }
            }
          },
          "package_url": {
            "type": "string",
            "format": "uri"
          },
          "package_url_expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "verify_url": {
            "type": "string",
            "description": "رابط إلى أداة التحقق العامة من ProofSnap.",
            "format": "uri"
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "PlainText": {
        "type": "string",
        "description": "قاعدة النص العادي للحقول النصية الحرة القصيرة التي تُطبع على مستندات موجهة إلى المحكمة أو على صفحات عامة (case_reference، case_id، case_note، قيم evidence_pack.custodian، و case_reference في certify-hash، وأمين الحيازة في التنقيح، وإقرار التصدير، وأسماء الشركات والقضايا). تُطبَّع القيمة وفق NFC، وتُدمج المسافات المتكررة وتُزال من البداية والنهاية، ويُحسب الطول بالأحرف (نقاط ترميز Unicode). المرفوض: أحرف التحكم (C0، C1)، وتضمينات bidi وتجاوزاته وعوازله (من U+202A إلى U+202E، ومن U+2066 إلى U+2069)، وعلامات الاتجاه (U+200E، U+200F، U+061C)، والأحرف صفرية العرض وغير المرئية (U+200B، من U+2060 إلى U+2064، U+FEFF، U+00AD، U+034F، U+180E، من U+206A إلى U+206F، من U+FFF9 إلى U+FFFB)، وأحرف الحشو في الهانغول (U+115F، U+1160، U+3164، U+FFA0)، وأحرف الوسوم (من U+E0000 إلى U+E007F)، والأحرف البديلة المنفردة (lone surrogates)، وأحرف الاستخدام الخاص. تُقبل حروف جميع أنظمة الكتابة، بما فيها مقاطع الهانغول والعربية والعبرية والديفاناغارية و CJK، ويُسمح بالحرفين U+200C / U+200D (ZWNJ، ZWJ) لأن التهجئة الصحيحة في عدة أنظمة كتابة تحتاج إليهما."
      },
      "EidasTimestamp": {
        "type": "object",
        "description": "نتيجة eIDAS عندما يُطلب include_eidas بمفتاح فعلي؛ وغير موجودة عندما لا يُطلب. confirmed: تحمل الحزمة manifest.json.tsr (يُحتسب في الفوترة، ما لم تكن حالة الإلغاء غير معروفة). not_obtained: لم يُضمَّن أي رمز مميز ولم يُحتسب شيء في الفوترة؛ ولا تحمل الحزمة إلا تثبيت OpenTimestamps (reason بقيمة token_refused: عاد رمز مميز ولم يجتز الفحص عند الإصدار؛ unavailable: تعذّر الحصول على أي رمز مميز؛ time_budget: لم يتسع الوقت المتبقي للطلب، فلم يُرسَل، أو رفضته خدمة الطوابع الزمنية قبل أي طلب إلى الجهة المصدِرة، أو أُوقف، وهو ما يحدث في مسار التنقيح المتزامن أو في نهاية عملية التقاط طويلة؛ account_limit: ليس للحساب فاتورة مدفوعة بعد وقد بلغ حد eIDAS البالغ 20 طابعًا في اليوم بتوقيت UTC أو 50 في الشهر بتوقيت UTC، محسوبةً على عمليات الالتقاط والدفعات والجداول وعمليات التوثيق والمقارنات والتنقيحات وختم القضايا مجتمعةً، لذا لم يُرسَل أي طلب؛ ولا تخضع الحسابات التي لديها فاتورة مدفوعة لهذا الحد). الصفوف المكتملة قبل 2026-10-01 لا تُبلغ إلا عن confirmed.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "confirmed",
              "not_obtained"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "token_refused",
              "unavailable",
              "time_budget",
              "account_limit"
            ],
            "description": "not_obtained فقط."
          },
          "serial": {
            "type": "string",
            "nullable": true,
            "description": "confirmed فقط."
          },
          "issued_at": {
            "type": "string",
            "nullable": true,
            "description": "confirmed فقط."
          },
          "revocation": {
            "type": "string",
            "enum": [
              "unknown"
            ],
            "description": "confirmed فقط، وفقط عندما يتعذر تحديد حالة شهادة TSA عند الإصدار (اجتاز الرمز المميز كل الفحوص الأخرى). يُضمَّن الرمز المميز في الحزمة ولا يُحتسب في الفوترة."
          },
          "billed": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "يظهر (false) مع حالة إلغاء unknown: لم يُحتسب هذا الرمز المميز في الفوترة."
          }
        }
      },
      "ExternalArchive": {
        "type": "object",
        "description": "نتيجة Internet Archive (Wayback Machine) لعملية التقاط. Internet Archive أرشيف عام تابع لجهة خارجية.",
        "required": [
          "requested"
        ],
        "properties": {
          "requested": {
            "type": "boolean",
            "description": "false عندما لم يُرسَل أي طلب إلى Internet Archive (external_archive: false، أو عملية توثيق أو مقارنة، أو عملية التقاط أُجريت قبل وجود الأرشفة، أو لم يتسع الوقت المخصص للطلب). وعندئذ تغيب جميع الحقول الأخرى."
          },
          "status": {
            "type": "string",
            "enum": [
              "created",
              "recent_existing",
              "created_after_request",
              "existing",
              "pending",
              "rate_limited",
              "failed",
              "unconfirmed",
              "unavailable",
              "skipped",
              "skipped_sensitive",
              "skipped_query"
            ],
            "description": "النتيجة الحالية. لمهمة Save Page Now التي لا تزال معلقة عند الختم، يقرأ المجدول حالة المهمة بعد نحو 5 و 30 دقيقة من الطلب، وتصبح هذه القيمة النتيجة النهائية (created، recent_existing، failed، rate_limited، أو unconfirmed عندما لا تصل إجابة خلال نافذة الفحص). تحمل القيم created، recent_existing، created_after_request و existing الحقل snapshot_url."
          },
          "status_at_sealing": {
            "type": "string",
            "enum": [
              "created",
              "recent_existing",
              "created_after_request",
              "existing",
              "pending",
              "rate_limited",
              "failed",
              "unconfirmed",
              "unavailable",
              "skipped",
              "skipped_sensitive",
              "skipped_query"
            ],
            "description": "الحالة المختومة في الحزمة (يسجلها metadata.json، forensic_log.json، chain_of_custody.json و evidence.pdf). الحزمة نفسها لا تتغير أبدًا؛ وتساوي status ما لم تحسم متابعةٌ لاحقة مهمةً معلقة."
          },
          "method": {
            "type": "string",
            "nullable": true,
            "enum": [
              "spn2_authenticated",
              "spn_anonymous",
              null
            ]
          },
          "requested_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "snapshot_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "النسخة العامة في Wayback Machine، ودائمًا بالشكل https://web.archive.org/web/{snapshot_timestamp}/{url}."
          },
          "snapshot_timestamp": {
            "type": "string",
            "nullable": true,
            "description": "الطابع الزمني لـ Wayback بالصيغة YYYYMMDDhhmmss (UTC)."
          },
          "snapshot_offset_seconds": {
            "type": "integer",
            "nullable": true,
            "description": "وقت اللقطة الأرشيفية مطروحًا منه وقت عملية الالتقاط، بالثواني (القيمة السالبة: قبل عملية الالتقاط)."
          },
          "resolved_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "وقت تسجيل متابعة المجدول للنتيجة النهائية؛ null عندما كانت الحالة المختومة نهائية."
          },
          "job_id": {
            "type": "string",
            "nullable": true,
            "description": "معرّف مهمة Save Page Now (للطلبات المصادق عليها)."
          },
          "follow_up_pending": {
            "type": "boolean",
            "description": "true ما دام على المجدول أن يقرأ حالة المهمة."
          },
          "skip_reason": {
            "type": "string",
            "nullable": true,
            "description": "سبب عدم إرسال عنوان URL، عندما تكون status بقيمة skipped أو skipped_sensitive أو skipped_query: credentials_in_url، sensitive_path، opaque_path_segment، sensitive_fragment، sensitive_parameter_name، opaque_parameter_value، query_string (أو الحالة نفسها في الصفوف الأقدم)."
          },
          "resolved": {
            "type": "object",
            "nullable": true,
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "created",
                  "recent_existing",
                  "failed",
                  "rate_limited",
                  "unresolved"
                ]
              },
              "job_status": {
                "type": "string",
                "nullable": true,
                "description": "success أو error أو pending كما قُرئت من مهمة Save Page Now، و null عندما تعذّرت قراءتها."
              },
              "snapshot_url": {
                "type": "string",
                "nullable": true,
                "description": "https://web.archive.org/web/<14 رقمًا>/<URL>، و null عندما يكون غير موجود أو غير صالح."
              },
              "snapshot_timestamp": {
                "type": "string",
                "nullable": true
              },
              "checked_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "reason": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "no_request_time",
                  "capture_deleted",
                  "capture_failed",
                  null
                ],
                "description": "سبب غياب إجابة المهمة: no_request_time (لا يحتوي الصف على وقت طلب تُجدول القراءات انطلاقًا منه)، capture_deleted أو capture_failed (نُقلت عملية الالتقاط إلى سلة المهملات أو فشلت بينما كانت المهمة معلقة، فلم تُتابع؛ وعملية الالتقاط المستعادة من سلة المهملات خلال 30 دقيقة من الطلب تُتابع مجددًا). null في غير ذلك."
              }
            },
            "description": "سجل المتابعة (السجل التاريخي) كإسقاط ثابت: snapshot_url فقط عندما يكون عنوان URL صالحًا للقطة Wayback الخاصة بـ snapshot_timestamp؛ و reason فقط عندما تنتهي المتابعة دون إجابة من المهمة."
          }
        }
      },
      "CaptureStorage": {
        "type": "object",
        "description": "مدة الاحتفاظ، والنسخة المكررة، و(لعمليات الالتقاط المجدولة) سجل الإصدارات.",
        "properties": {
          "retention": {
            "type": "object",
            "properties": {
              "years": {
                "type": "integer",
                "example": 10
              },
              "counted_from": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "الأحدث من بين created_at وآخر استخدام معروف: آخر فحص وجد الصفحة دون تغيير، أو اللحظة التي حلّت فيها عملية التقاط أحدث من الجدول نفسه محل هذه العملية."
              },
              "earliest_deletion_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "counted_from مضافًا إليه years، تقريبيًا: يُنفَّذ الحذف في جولة لاحقة للمجدول، وقد تُغيّر الفحوص اللاحقة هذا الموعد. null عند وجود حفظ قانوني."
              },
              "legal_hold": {
                "type": "boolean"
              }
            }
          },
          "duplicate_copy": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "none",
                  "pending",
                  "stored",
                  "not_copied"
                ],
                "description": "stored: تُحفظ نسخة ثانية من الحزمة (ومن إثبات OTS المحدَّث الخاص بها) في حاوية تخزين (bucket) منفصلة في منطقة أخرى داخل الاتحاد الأوروبي، وتُحذف مع الأصل. pending: جارٍ النسخ (دقائق). none: حزم الاختبار، والحزم السابقة لوجود النسخة المكررة إلى أن تُنسخ."
              },
              "copied_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          },
          "version": {
            "type": "object",
            "description": "لعمليات الالتقاط المجدولة فقط.",
            "properties": {
              "superseded_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "superseded_by": {
                "type": "string",
                "nullable": true
              },
              "unchanged_first_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "أول فحص (on_change) وجد نص الصفحة دون تغيير منذ عملية الالتقاط هذه."
              },
              "unchanged_last_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "unchanged_checks": {
                "type": "integer",
                "description": "الفحوص المسجلة التي لم تجد تغييرًا (بحد أقصى فحص واحد في الساعة)."
              }
            }
          }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "capture.completed",
          "capture.failed",
          "certify.completed",
          "certify.failed",
          "redaction.completed",
          "redaction.failed",
          "monitor.change_detected",
          "capture.anchor_confirmed",
          "schedule.paused"
        ],
        "description": "حدث من الكتالوج (الوسم Webhooks)."
      },
      "WebhookSubscriptionFilters": {
        "type": "object",
        "description": "قيمة واحدة على الأكثر من host و schedule_id. وعندئذ لا يتلقى الاشتراك إلا أحداث الصفوف ذات هذا host (اسم المضيف بأحرف صغيرة لعنوان URL الملتقط) أو أحداث هذا الجدول. `{}` لعدم التصفية.",
        "properties": {
          "host": {
            "type": "string",
            "example": "www.example.com"
          },
          "schedule_id": {
            "type": "string",
            "example": "sch_a1B2c3D4e5F6g7H8"
          }
        }
      },
      "WebhookSubscription": {
        "type": "object",
        "properties": {
          "subscription_id": {
            "type": "string",
            "example": "whs_a1B2c3D4e5F6g7H8"
          },
          "url": {
            "type": "string",
            "description": "نقطة النهاية (endpoint) بـ https التي تُرسل إليها الأحداث بطريقة POST."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "filters": {
            "$ref": "#/components/schemas/WebhookSubscriptionFilters"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "env": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "بيئة بيانات الاعتماد التي أنشأته. الاشتراك الفعلي لا يتلقى إلا أحداث الصفوف الفعلية، واشتراك الاختبار لا يتلقى إلا أحداث صفوف الاختبار."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "type": "string",
            "enum": [
              "key",
              "session"
            ],
            "description": "key: أُنشئ بمفتاح API؛ وإبطال هذا المفتاح يعطّل الاشتراك (disabled_reason بقيمة key_revoked)."
          },
          "consecutive_failures": {
            "type": "integer",
            "description": "عمليات التسليم الفاشلة منذ آخر تسليم ناجح."
          },
          "last_success_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_failure_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_error": {
            "type": "string",
            "nullable": true,
            "description": "فئة الخطأ لآخر عملية تسليم فاشلة (timeout، connection_failed، blocked_address، redirect_not_followed، http_<status>)."
          },
          "disabled_reason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "gone",
              "failing",
              "key_revoked",
              null
            ],
            "description": "gone: استجابت نقطة النهاية بالرمز 410. failing: فشلت كل عمليات التسليم لمدة 5 أيام دون أي نجاح. key_revoked: أُبطل مفتاح API الذي أنشأه. يعيد PATCH بالقيمة status active تفعيله."
          },
          "disabled_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "required": [
          "subscription_id",
          "url",
          "events",
          "filters",
          "description",
          "status",
          "env",
          "created_at",
          "created_by",
          "consecutive_failures",
          "last_success_at",
          "last_failure_at",
          "last_error",
          "disabled_reason",
          "disabled_at"
        ]
      },
      "WebhookSubscriptionCreated": {
        "type": "object",
        "properties": {
          "subscription_id": {
            "type": "string",
            "example": "whs_a1B2c3D4e5F6g7H8"
          },
          "url": {
            "type": "string",
            "description": "نقطة النهاية (endpoint) بـ https التي تُرسل إليها الأحداث بطريقة POST."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "filters": {
            "$ref": "#/components/schemas/WebhookSubscriptionFilters"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "env": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "بيئة بيانات الاعتماد التي أنشأته. الاشتراك الفعلي لا يتلقى إلا أحداث الصفوف الفعلية، واشتراك الاختبار لا يتلقى إلا أحداث صفوف الاختبار."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "type": "string",
            "enum": [
              "key",
              "session"
            ],
            "description": "key: أُنشئ بمفتاح API؛ وإبطال هذا المفتاح يعطّل الاشتراك (disabled_reason بقيمة key_revoked)."
          },
          "secret": {
            "type": "string",
            "description": "whsec_ متبوعًا بترميز base64 قياسي. يُعرض مرة واحدة فقط: تُوقَّع به كل عملية تسليم إلى هذا الاشتراك (Standard Webhooks). خزّنه الآن؛ إذ لا يمكن قراءته لاحقًا. إعادة الإرسال بالقيمة نفسها لـ Idempotency-Key خلال 24 ساعة تُعيد الجسم نفسه، بما في ذلك السر."
          }
        },
        "required": [
          "subscription_id",
          "url",
          "events",
          "filters",
          "description",
          "status",
          "env",
          "created_at",
          "created_by",
          "secret"
        ]
      },
      "Event": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "evt_a1B2c3D4e5F6g7H8"
          },
          "type": {
            "$ref": "#/components/schemas/WebhookEventType"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "env": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "data": {
            "type": "object",
            "description": "جسم الحدث كما تتلقاه الاشتراكات، دون subscription_id ودون الروابط الموقّعة مسبقًا (package_url، package_url_expires_at، upgraded_proof_url، compare.package_url)."
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "env",
          "data"
        ]
      },
      "PrivateNote": {
        "type": "string",
        "nullable": true,
        "maxLength": 200,
        "description": "ملاحظتك الخاصة على عملية الالتقاط (مثل «التقاط من الجوال»)، بحد أقصى 200 حرف، نص عادي (قاعدة PlainText). تُخزَّن في سجل عملية الالتقاط فقط: فهي ليست جزءًا من حزمة الأدلة المختومة (لا في ملف ZIP ولا في PDF ولا في البيان ولا في أي بصمة) وليس لها أي قيمة إثباتية، لذا يمكن تغييرها أو مسحها لاحقًا عبر PATCH /v1/captures/{capture_id} دون التأثير في الأدلة. لا يقرؤها إلا منشئ عملية الالتقاط. لعدم إضافة ملاحظة، احذف الحقل أو أرسل null أو سلسلة فارغة.",
        "example": "mobile capture"
      },
      "CaptureEvent": {
        "type": "object",
        "description": "حدث واحد في سلسلة أحداث عمليات الالتقاط للحساب. hash = SHA-256 (hex) لصيغة JSON المعيارية (canonical) للحقول المشمولة بحساب البصمة (v، chain، seq، type، capture_id، manifest_sha256، actor، access، detail، at، prev_hash؛ المفاتيح مرتبة، بلا مسافات، والقيم الغائبة كـ null). prev_hash هو hash الحدث السابق، و 64 صفرًا لـ seq 1.",
        "properties": {
          "v": {
            "type": "integer",
            "enum": [
              1
            ]
          },
          "chain": {
            "type": "string",
            "description": "الحساب: uid لعمليات الالتقاط الشخصية، و org_<org_id> لعمليات التقاط الشركة."
          },
          "seq": {
            "type": "integer",
            "minimum": 1
          },
          "type": {
            "type": "string",
            "enum": [
              "trash",
              "restore",
              "erase",
              "purge",
              "retention_deletion",
              "account_deletion",
              "company_deletion",
              "legal_hold_on",
              "legal_hold_off",
              "s3_legal_hold",
              "import"
            ]
          },
          "capture_id": {
            "type": "string"
          },
          "manifest_sha256": {
            "type": "string",
            "nullable": true,
            "description": "بصمة SHA-256 لملف manifest.json الخاص بعملية الالتقاط، تُقرأ قبل أي حذف."
          },
          "actor": {
            "type": "string",
            "description": "uid الحساب المنفِّذ، أو system لجولات الخادم الدورية."
          },
          "access": {
            "type": "string",
            "description": "author أو company_owner أو matter_owner أو system."
          },
          "detail": {
            "type": "string",
            "nullable": true,
            "description": "legal_hold_on / legal_hold_off: مصدر الحفظ (manual، bulk، matter:<id>، derived:<capture_id>)، أو في حالة رفعه بواسطة النظام (actor system) رمز السبب subscription_ended؛ s3_legal_hold: نتيجة S3 (on، off، failed، unsupported، none)؛ import: نوع الحزمة المستوردة. لا نص حر."
          },
          "at": {
            "type": "string",
            "format": "date-time",
            "description": "وقت كتابة الحدث في السلسلة. لا يسبق أبدًا قيمة at للحدث السابق."
          },
          "prev_hash": {
            "type": "string"
          },
          "hash": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "description": "وقت تثبيت الإجراء نفسه (معاملة تغيير الحالة). لا يدخل في حساب البصمة. يساوي عادةً at بفارق أجزاء من الثانية؛ ويتأخر فقط عندما يعيد الخادم محاولة الإلحاق بالسلسلة (يُسجَّل الإجراء في المعاملة نفسها التي يتم فيها التغيير، فلا يضيع أي إجراء)."
          }
        }
      },
      "CaptureEventAnchor": {
        "type": "object",
        "description": "تثبيت يومي للسلسلة عبر OpenTimestamps: head_hash هو بصمة الحدث to_seq، وهي تتضمن الالتزام بكل الأحداث السابقة.",
        "properties": {
          "anchor_id": {
            "type": "string"
          },
          "day": {
            "type": "string",
            "format": "date"
          },
          "from_seq": {
            "type": "integer"
          },
          "to_seq": {
            "type": "integer"
          },
          "head_hash": {
            "type": "string"
          },
          "ots_proof_base64": {
            "type": "string",
            "nullable": true,
            "description": "إثبات OpenTimestamps (ملف .ots، بترميز base64) على head_hash."
          },
          "ots_status": {
            "type": "string",
            "enum": [
              "pending",
              "bitcoin",
              "unconfirmed"
            ],
            "description": "pending: يحمل ots_proof_base64 التزام خوادم التقويم (يستعلم ots verify من خوادم التقويم)؛ bitcoin: استُبدل بالإثبات المحدَّث إلى كتلة Bitcoin (bitcoin_block_height)؛ unconfirmed: لم يُعَد إثبات Bitcoin خلال 14 يومًا، ولا يزال الإثبات المخزن قابلًا للتحقق عبر خوادم التقويم."
          },
          "bitcoin_block_height": {
            "type": "integer",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ImportSummary": {
        "type": "object",
        "description": "المستويات الثلاثة للحزمة المتحقق منها.",
        "properties": {
          "kind_label_key": {
            "type": "string",
            "description": "مفتاح تسمية نوع الحزمة، import_kind_<kind>."
          },
          "integrity": {
            "type": "string",
            "enum": [
              "verified",
              "failed"
            ]
          },
          "time_anchor": {
            "type": "string",
            "enum": [
              "eidas_qualified",
              "eidas_qualified_revocation_unknown",
              "ots_bitcoin_attested_unconfirmed",
              "ots_pending",
              "none"
            ]
          },
          "provenance": {
            "type": "string",
            "nullable": true,
            "enum": [
              "issued_by_proofsnap",
              "unverified",
              "registered_by_extension_account",
              "not_registered",
              "unknown",
              null
            ]
          },
          "registered_at": {
            "type": "string",
            "format": "date-time",
            "description": "وقت تسجيل سجل الإضافة (extension) للحزمة (provenance بقيمة registered_by_extension_account)."
          },
          "issued_at": {
            "type": "string",
            "format": "date-time",
            "description": "وقت تسجيل الإصدار في السجل العام للحزم الصادرة في API (provenance بقيمة issued_by_proofsnap)."
          }
        }
      },
      "Import": {
        "type": "object",
        "properties": {
          "import_id": {
            "type": "string",
            "example": "imp_a1B2c3D4e5F6g7H8i9J0"
          },
          "status": {
            "type": "string",
            "enum": [
              "uploading",
              "verifying",
              "accepted",
              "rejected",
              "duplicate",
              "expired"
            ]
          },
          "filename": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "upload_expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "uploading فقط: أكمل قبل هذا الوقت (بعد 24 ساعة من الإنشاء)."
          },
          "finished_at": {
            "type": "string",
            "format": "date-time",
            "description": "وقت وصول الاستيراد إلى حالته النهائية."
          },
          "capture_id": {
            "type": "string",
            "description": "accepted فقط: عملية الالتقاط الجديدة (بالمعرّف نفسه الخاص بالاستيراد)."
          },
          "duplicate_of": {
            "type": "string",
            "description": "duplicate فقط: عملية الالتقاط الخاصة بك التي تحتوي هذا البيان بالفعل."
          },
          "kind": {
            "type": "string",
            "enum": [
              "web_capture_v21",
              "web_capture_legacy",
              "session",
              "file_certification_single",
              "file_certification_combined",
              "api_capture",
              "api_certify",
              "api_compare",
              "api_redaction"
            ],
            "description": "نوع الحزمة، بعد اكتشافه."
          },
          "summary": {
            "$ref": "#/components/schemas/ImportSummary"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "rejected و expired: رموز الأسباب، مثل file_mismatch، file_missing، unlisted_file، bad_signature، no_hashed_files، unknown_schema، zip_too_large، test_package، ots_digest_mismatch، eidas_token_failed، forensic_chain_broken، size_mismatch، upload_not_completed، verification_incomplete. رموز فقط، ولا أسماء ملفات أبدًا."
          },
          "zip_sha256": {
            "type": "string",
            "description": "بصمة SHA-256 لملف ZIP المرفوع، بعد حسابها."
          },
          "manifest_sha256": {
            "type": "string",
            "description": "بصمة SHA-256 لملف manifest.json الخاص بالحزمة (accepted و duplicate)."
          },
          "matter_id": {
            "type": "string",
            "description": "القضية المحددة عند إنشاء الاستيراد."
          },
          "matter_assignment": {
            "type": "string",
            "enum": [
              "assigned",
              "skipped"
            ],
            "description": "accepted مع matter_id: هل أُضيفت عملية الالتقاط إلى القضية (تُتخطى الإضافة إذا كانت القضية حينها مختومة أو مؤرشفة أو لم تعد مفتوحة لك؛ ويبقى الاستيراد accepted)."
          },
          "upload": {
            "type": "object",
            "description": "فقط مع ?upload=true ما دامت status بقيمة uploading.",
            "properties": {
              "upload_id": {
                "type": "string"
              },
              "part_size": {
                "type": "integer"
              },
              "parts": {
                "type": "array",
                "description": "عناوين URL موقّعة مسبقًا لطلبات PUT للأجزاء التي لم تُستلم بعد؛ أرسل عبر PUT بالضبط size_bytes.",
                "items": {
                  "type": "object",
                  "properties": {
                    "part_number": {
                      "type": "integer"
                    },
                    "url": {
                      "type": "string"
                    },
                    "size_bytes": {
                      "type": "integer"
                    }
                  }
                }
              },
              "expires_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "received": {
                "type": "array",
                "nullable": true,
                "items": {
                  "type": "object",
                  "properties": {
                    "part_number": {
                      "type": "integer"
                    },
                    "etag": {
                      "type": "string"
                    },
                    "size_bytes": {
                      "type": "integer"
                    }
                  }
                }
              },
              "completed": {
                "type": "boolean",
                "description": "اكتمل الرفع بالفعل: أرسل complete مرة أخرى."
              }
            }
          }
        }
      },
      "ImportVerification": {
        "type": "object",
        "description": "تقرير التحقق المسجل عند الاستيراد (دون محتويات الملفات، ودون تفاصيل نصية حرة).",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "kind": {
            "type": "string",
            "enum": [
              "web_capture_v21",
              "web_capture_legacy",
              "session",
              "file_certification_single",
              "file_certification_combined",
              "api_capture",
              "api_certify",
              "api_compare",
              "api_redaction"
            ]
          },
          "format": {
            "type": "string"
          },
          "kind_label_key": {
            "type": "string"
          },
          "evidence_id": {
            "type": "string",
            "description": "المعرّف الذي تحمله الحزمة (ps_...، FC-...، cap_...، cert_...، cmp_...، red_...)."
          },
          "manifest_sha256": {
            "type": "string"
          },
          "zip_sha256": {
            "type": "string"
          },
          "anchor_sha256": {
            "type": "string",
            "description": "البصمة التي تغطيها الطوابع الزمنية: بصمة البيان، أو في توثيق ملف واحد بصمة الملف الموثَّق."
          },
          "anchor_source": {
            "type": "string",
            "enum": [
              "manifest",
              "certified_file"
            ]
          },
          "checks": {
            "type": "object",
            "description": "structure, signature, files (listed, verified, presence_only, unhashed, os_junk_ignored), custody, forensic_chain, ots, eidas, test_mode.",
            "additionalProperties": true
          },
          "summary": {
            "$ref": "#/components/schemas/ImportSummary"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "CaptureImport": {
        "type": "object",
        "description": "فقط في عملية التقاط قيمة source فيها \"import\".",
        "properties": {
          "import_id": {
            "type": "string"
          },
          "package_kind": {
            "type": "string",
            "enum": [
              "web_capture_v21",
              "web_capture_legacy",
              "session",
              "file_certification_single",
              "file_certification_combined",
              "api_capture",
              "api_certify",
              "api_compare",
              "api_redaction"
            ]
          },
          "kind_label_key": {
            "type": "string"
          },
          "package_format": {
            "type": "string"
          },
          "original_evidence_id": {
            "type": "string"
          },
          "original_filename": {
            "type": "string",
            "nullable": true
          },
          "imported_at": {
            "type": "string",
            "format": "date-time"
          },
          "captured_at_basis": {
            "type": "string",
            "enum": [
              "package_claim"
            ],
            "description": "captured_at للحزمة المستوردة هو الوقت الذي تذكره الحزمة، ولا يكون أبدًا وقتًا رصده ProofSnap."
          },
          "manifest_sha256": {
            "type": "string"
          },
          "zip_sha256": {
            "type": "string"
          },
          "summary": {
            "$ref": "#/components/schemas/ImportSummary"
          },
          "levels": {
            "type": "object",
            "properties": {
              "integrity": {
                "type": "string"
              },
              "time_anchor": {
                "type": "string"
              },
              "provenance": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "verification": {
            "$ref": "#/components/schemas/ImportVerification"
          },
          "provenance": {
            "type": "object",
            "properties": {
              "level": {
                "type": "string"
              },
              "source": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "api_ledger",
                  "extension_registry",
                  null
                ]
              },
              "issued_at": {
                "type": "string",
                "nullable": true
              },
              "registered_at": {
                "type": "string",
                "nullable": true
              },
              "reason": {
                "type": "string"
              }
            }
          }
        }
      },
      "Rule902DisabledWarnings": {
        "type": "array",
        "description": "يظهر فقط عندما يطلب الطلب قالب الإقرار (include_rule_902: true، أو لعملية التقاط، قيمة evidence_pack.custodian غير فارغة) بينما القالب معطّل. قُبل الطلب وتُجوهلت العلامة: لا يُنشأ rule_902_certification.pdf.",
        "items": {
          "type": "object",
          "properties": {
            "code": {
              "type": "string",
              "enum": [
                "RULE902_DISABLED"
              ]
            },
            "message": {
              "type": "string",
              "example": "The FRE 902 certification template (rule_902_certification.pdf) is currently disabled. include_rule_902 was accepted and ignored: the package does not contain the template."
            }
          },
          "required": [
            "code",
            "message"
          ]
        }
      }
    }
  }
}
