> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# إلغاء الاشتراك وقوائم الحظر

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

# إلغاء الاشتراك وقوائم الحظر

**قائمة الحظر** هي مجموعة العناوين التي يجب عدم مراسلتها مرة أخرى —
الأشخاص الذين ردّوا بـ STOP، أو ألغوا الاشتراك، أو ارتدّت رسائلهم، أو
اشتكوا. احترامها متطلب قانوني على كل قناة منظَّمة، ويتعامل Orbit معها
كبوابة إرسال صارمة: يُسقَط العنوان المحظور قبل الإرسال بغض النظر عن
الحملة أو استيراد جهات الاتصال أو استدعاء API.

تغطي هذه الصفحة كيفية عمل الحظر، وكيفية **الاستيراد الجماعي** لقائمة
حظر موجودة — على سبيل المثال عند الترحيل من منصة أخرى — عبر رفع ملف CSV
واحد، وكيفية [تصدير السجل للخارج](#تصدير-قائمة-الحظر) لغرض التدقيق.

جميع نقاط النهاية أدناه متجذّرة في
`https://api.orbit.devotel.io/api/v1/compliance`.

***

## كيف يحدث الحظر

ينتهي عنوانٌ ما في قائمة الحظر بعدّة طرق:

* ردّ جهة اتصال بكلمة **STOP** المفتاحية على SMS/WhatsApp.
* إلغاء اشتراك جهة اتصال عبر
  [مركز التفضيلات](/compliance/send-gates#preference-center).
* تسجيل إلغاء اشتراك عبر
  [Consent API](/compliance/consent-management) (`opt_in: false`).
* **استيراد قائمة جماعياً** (هذه الصفحة).

لكل إدخال **نطاق قناة**. مجموعة النطاقات الكاملة هي: `all` و`sms`
و`voice` و`whatsapp` و`email` و`push` و`telegram` و`messenger` و`rcs`.

يعتمد اختيار النطاق على نقطة الدخول:

* **الاستيراد الجماعي عبر CSV** يستنتج النطاق من نوع العنوان في كل صف:
  عناوين الهاتف وWhatsApp تكون افتراضياً بنطاق `all` — إشارة STOP على
  رقم هاتف تحظر كل قناة يمكن الوصول إليها على ذلك الرقم — بينما
  عناوين البريد الإلكتروني تُنطِق بـ `email`. عمود `channel` يتجاوز هذا
  لكل صف (انظر [استيراد CSV جماعي](#استيراد-csv-جماعي)).
* **Consent API ومركز التفضيلات** يحظران دائماً النطاق `all`، بغض
  النظر عما إذا كان المعرف المسجَّل رقم هاتف أو عنوان بريد إلكتروني.
  إلغاء الاشتراك عبر أي من هاتين نقطتي الدخول يزيل جهة الاتصال من كل
  قناة.

<Note>
  أياً كانت طريقة حظر رقم الهاتف، فإن بوابتي الصوت والمُتصل يحترمانه:
  الرقم الذي يلغي الاشتراك على أي قناة يتوقف عن استقبال المكالمات كما
  الرسائل. تختلف الآلية بحسب نقطة الدخول. **الاستيراد الجماعي عبر CSV**
  يُنعكِس بالإضافة إلى ذلك لصفوف الهاتف على قائمة DNC ويُعلِّم جهات
  الاتصال المطابقة. أما إلغاء الاشتراك عبر **كلمة STOP المفتاحية** أو
  **مركز التفضيلات** أو **Consent API** فيُسجَّل بالنطاق `all`، الذي
  تقرؤه بوابتا الصوت والمُتصل من قائمة الحظر مباشرة — لا تزال المكالمة
  محظورة، لكن لا يُكتب صف مستقل في قائمة DNC ولا علم على جهة الاتصال.
</Note>

***

## استيراد CSV جماعي

`POST /compliance/suppression-list/import` يقبل رفع
`multipart/form-data` لملف CSV. يتطلب مفتاح admin أو owner وهو محدود
بمعدل 5 طلبات/الدقيقة.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/compliance/suppression-list/import \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -F "file=@suppressions.csv" \
  -F "default_country=US" \
  -F "default_reason=migrated_from_legacy_platform" \
  -F "dry_run=false"
```

### حقول النموذج

| الحقل             | النوع   | ملاحظات                                                                                           |
| ----------------- | ------- | ------------------------------------------------------------------------------------------------- |
| `file`            | file    | **إلزامي.** ملف CSV واحد، ≤ 25 ميغابايت، ≤ 100,000 صف.                                            |
| `default_country` | string  | ISO-3166-1 alpha-2. يُستخدم لمعايرة أرقام الهاتف بالصيغة الوطنية إلى E.164.                       |
| `default_reason`  | string  | يُطبَّق على كل صف مقبول (≤ 512 حرفاً).                                                            |
| `dry_run`         | boolean | عند `true`، تحليل وتصنيف فقط — لا كتابة في قاعدة البيانات. استخدمه لمعاينة ملف قبل تثبيت العملية. |

### تنسيق CSV

الصف الأول هو ترويسة. أسماء الأعمدة **غير حسّاسة لحالة الأحرف**
و**مستقلّة عن الموضع**، والأسماء المستعارة الشائعة مقبولة:

| العمود المنطقي         | الترويسات المقبولة                         |
| ---------------------- | ------------------------------------------ |
| الهاتف                 | `phone`, `phonenumber`, `mobile`, `msisdn` |
| البريد الإلكتروني      | `email`, `emailaddress`, `mail`            |
| معرّف WhatsApp         | `wa_id`, `whatsapp`, `whatsappid`          |
| السبب (اختياري)        | `reason`, `note`, `notes`                  |
| القناة (تجاوز اختياري) | `channel`                                  |

يجب أن يحتوي كل صف على **واحد على الأقل** من phone / email / wa\_id.
قد يحمل الصف الواحد عدّة أنواع عناوين — وكل نوع ينتج إدخال حظر خاصًّا
به. مثال:

```csv theme={null}
phone,email,reason
+14155550101,,replied STOP
,jordan@example.com,unsubscribed via email
+442071838750,sam@example.co.uk,complaint
```

إذا كان عمود `channel` موجوداً فإنه يتجاوز النطاق الافتراضي لذلك الصف
ويجب أن يكون أحد قيم النطاق المذكورة أعلاه.

### نتائج كل صف

يُبلِغ الرد عن النتائج لكل صف. الصفوف المقبولة تُكتَب؛ وأما الباقي
فُتصنَّف، ولا تُسقَط بصمت أبداً.

```json theme={null}
{
  "data": {
    "run_id": "supimp_4d…",
    "total_rows": 1000,
    "accepted": 950,
    "duplicates": 30,
    "intra_file_duplicates": 15,
    "invalid": 5,
    "errors": [
      { "status": "invalid", "reason": "invalid_phone", "raw_line": 42 }
    ],
    "by_channel": { "all": 800, "email": 150 },
    "file_sha256": "9b2e…"
  },
  "meta": { "request_id": "…", "timestamp": "2026-06-08T12:00:00.000Z" }
}
```

| العدّاد                 | المعنى                                                                  |
| ----------------------- | ----------------------------------------------------------------------- |
| `accepted`              | صفوف كُتِبت حديثاً في قائمة الحظر.                                      |
| `intra_file_duplicates` | صفوف تُكرِّر نفس `‫(channel, address)‬` السابق **داخل هذا الملف نفسه**. |
| `duplicates`            | صفوف محظورة مسبقاً من استيراد **سابق** (تُتخطّى، لا عملية).             |
| `invalid`               | صفوف فشلت في التحقق — انظر `errors[]`.                                  |
| `by_channel`            | أعداد المقبول مجمّعة حسب نطاق القناة.                                   |
| `file_sha256`           | بصمة محتوى الرفع، تُسجَّل للتدقيق.                                      |

<Note>
  يُبلَغ عدّادا التكرار **منفصلين وعن قصد**: `intra_file_duplicates`
  هي التكرارات داخل الملف الذي رفعته للتو، بينما `duplicates` كانت على
  قائمتك من قبل. لا يُعدّ أيّ منهما خطأً، ولا يُبتلَع أيّ منهما بصمت —
  كلاهما يُحسَب حتى يتطابق التسوية لديك.
</Note>

### أسباب التحقق

يحمل كل إدخال `errors[]` `reason` واضحاً و`raw_line` المصدر حتى تتمكن
من التصحيح وإعادة الرفع:

| `reason`           | الأصل                                              |
| ------------------ | -------------------------------------------------- |
| `missing_address`  | الصف لم يحتوِ على هاتف أو بريد إلكتروني أو wa\_id. |
| `invalid_phone`    | تعذّر معايرة الهاتف إلى E.164.                     |
| `invalid_email`    | فشل البريد الإلكتروني في التحقق من شكل RFC-5321.   |
| `invalid_wa_id`    | معرّف WhatsApp لم يكن رقم E.164 صالحاً.            |
| `row_too_long`     | تجاوزت خلية 4096 حرفاً.                            |
| `too_many_columns` | احتوى الصف على أكثر من 32 عموداً.                  |

### الحدود

| الحد                       | القيمة                                                              |
| -------------------------- | ------------------------------------------------------------------- |
| الحد الأقصى لحجم الملف     | 25 ميغابايت                                                         |
| الحد الأقصى للصفوف لكل طلب | 100,000                                                             |
| الحد الأقصى لطول الخلية    | 4,096 حرف                                                           |
| الحد الأقصى للأعمدة لكل صف | 32                                                                  |
| الحد الأقصى لطول السبب     | 512 حرف                                                             |
| مهلة الخادم                | 60 ثانية (الاستيراد الجزئي يعيد `408` مع الأعداد المعالجة حتى الآن) |

للأحجام التي تتجاوز 100,000 صف، قسِّم الملف واستورِد على دفعات —
كشف التكرار يعني أن إعادة استيراد النطاقات المتداخلة آمنة.

### عند فشل الاستيراد

شخِّص الأعطال على مستويين: **رفض على مستوى HTTP** (لا يُكتَب شيء)
و**تصنيفات على مستوى الصف** (الملف مقبول لكن صفوفاً محدَّدة ليست كذلك).

رفضات مستوى HTTP:

| الحالة                       | المعنى                                                                               | كيفية الإصلاح                                                                                                           |
| ---------------------------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
| `400 NO_FILE`                | لا يوجد جزء `file` في جسم multipart.                                                 | أرفِق CSV كحقل multipart باسم `file`.                                                                                   |
| `400 MULTIPLE_FILES`         | أكثر من ملف واحد مرفق.                                                               | أرسِل CSV واحداً لكل طلب.                                                                                               |
| `400 CSV_PARSE_ERROR`        | اقتباس مُشوَّه — `"` غير منتهية أو صف ترويسة مفقود.                                  | أعِد التصدير كـ RFC-4180 CSV (UTF-8)؛ تحقّق أن لكل اقتباس زوج إغلاق.                                                    |
| `400 MULTIPART_PARSE_FAILED` | الجسم لم يكن multipart/form-data صالحاً.                                             | عيِّن `Content-Type: multipart/form-data` ولا تُرمِّز الجسم مسبقاً.                                                     |
| `408 IMPORT_TIMEOUT`         | تجاوز التشغيل ميزانية 60 ثانية. يذكر الرد الصفوف المحظورة بالفعل؛ والباقي ليست كذلك. | قسِّم البقية إلى ملفات أصغر وأعِد التشغيل — الصفوف المحظورة قبل انتهاء المهلة تُبلَّغ كـ `duplicates`.                  |
| `413 PAYLOAD_TOO_LARGE`      | تجاوز الملف 25 ميغابايت.                                                             | قسِّم إلى ملفات أقل من 25 ميغابايت لكلٍّ منها.                                                                          |
| `413 TOO_MANY_ROWS`          | الملف المُحلَّل يتجاوز 100,000 صف.                                                   | قسِّم إلى ملفات بحدّ أقصى 100,000 صف لكلٍّ منها.                                                                        |
| `415 UNSUPPORTED_MEDIA_TYPE` | الرفع لم يكن CSV (تصدير `.xlsx` هو المُسبِّب الشائع).                                | أعِد التصدير كـ CSV (UTF-8).                                                                                            |
| `422 MISSING_ADDRESS_COLUMN` | صف الترويسة لا يحتوي على عمود عنوان قابل للتعرف.                                     | أدرِج واحداً على الأقل من `phone` أو `email` أو `wa_id` كترويسة (الأسماء المستعارة مذكورة تحت [تنسيق CSV](#تنسيق-csv)). |
| `422 VALIDATION_ERROR`       | فشل حقل نموذج اختياري في التحقق.                                                     | تحقّق أن `default_country` رمز ISO من حرفين و`default_reason` بطول ≤ 512 حرفاً.                                         |
| `429`                        | أكثر من 5 طلبات استيراد في دقيقة واحدة.                                              | انتظَر حتى تُغلَق النافذة ثم أعِد المحاولة — اجعل الدفعات الجماعية في قائمة انتظار بدلاً من الإرسال الكثيف.             |

تصنيفات مستوى الصف (إدخالات `errors[]` المصاحبة لاستيراد ناجح)
تُرسَم إلى الأسباب كما يلي:

| `reason`           | الأصل                                                                                                      | كيفية الإصلاح                                                                      |
| ------------------ | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `missing_address`  | الصف لم يحتوِ على هاتف أو بريد إلكتروني أو wa\_id — أو أن تجاوز `channel` لم يكن أحد النطاقات المسموح بها. | املأ خلية عنوان واحدة على الأقل؛ وقيِّد عمود `channel` بقيم النطاق المذكورة أعلاه. |
| `invalid_phone`    | تعذّر معايرة الهاتف إلى E.164.                                                                             | صحِّح الرقم، أو مرِّر `default_country` حتى تُحلَّل الأرقام بالصيغة الوطنية.       |
| `invalid_email`    | فشل البريد الإلكتروني في التحقق من شكل RFC-5321 (أو تجاوز 254 حرفاً).                                      | صحِّح العنوان؛ المسافات الزائدة و`@` المفقودة هما المُسبِّبان المعتادان.           |
| `invalid_wa_id`    | معرّف WhatsApp لم يكن رقم E.164 صالحاً.                                                                    | استخدِم MSISDN المستلم بصيغة E.164 (علامة `+` الابتدائية اختيارية).                |
| `row_too_long`     | تجاوزت خلية 4,096 حرفاً.                                                                                   | اختصِر الخلية — عادةً ما يكون قد لُصِق كتلة نصية في العمود الخطأ.                  |
| `too_many_columns` | احتوى الصف على أكثر من 32 عموداً.                                                                          | أعِد التصدير بفاصل واحد؛ الفواصل غير المقتبسة داخل خلية تقسِّمها إلى أعمدة وهمية.  |

تُعاد أول 100 إدخال `errors[]` فقط بالتفاصيل الكاملة — وعدّاد
`invalid` يعكس دائماً الإجمالي الحقيقي. يحزِّم معالج لوحة التحكم
(أدناه) الصفوف الظاهرة كملف `skipped.csv` قابل للتنزيل حتى تتمكن من
تصحيح وإعادة استيراد الإخفاقات فقط.

### الاستيراد من لوحة التحكم

نفس نقطة النهاية مغلَّفة بمعالج موجَّه عند **Settings → Compliance →
Opt-out lists → Import suppression list** — نفس عقد CSV، ولا حاجة إلى
طرفية.

1. **اختر ملف CSV** — اختر `.csv` أقل من 25 ميغابايت. استخدِم
   **Download sample CSV** في الحوار للحصول على ملف بداية مُنسَّق
   مسبقاً.
2. **عيِّن الافتراضيات (اختياري)** — `Default country` (ISO alpha-2)
   لمعايرة الأرقام بالصيغة الوطنية، ونص حر `Reason` يُختَم على كل صف
   مقبول.
3. **معاينة** — يُشغِّل الاستيراد كتشغيل تجريبي من جهة الخادم: لا
   يُكتَب شيء، ويعرض الحوار تفصيل المقبول / المُدرَج مسبقاً /
   التكرارات داخل الملف / غير الصالح قبل أن تُثبِّت.
4. **تأكيد الاستيراد** — ينفِّذ الكتابة المُثبِّتة. إذا كانت أي صفوف
   غير صالحة، قم بتنزيل **`skipped.csv`** لتصحيحها وإعادة استيرادها.

يفرض المعالج أيضاً فحوصات نوع الملف و25 ميغابايت من جهة العميل،
فيَفشُل التصدير الخطأ قبل أن يصل إلى API أصلاً.

***

## تصدير قائمة الحظر

`GET /compliance/suppression-list/export` يُنزِّل سجل الحظر — المقابل
المتماثل [للاستيراد](#استيراد-csv-جماعي) أعلاه. استخدِمه لإثبات
للجهة التنظيمية أو المدقّق أيّ العناوين كانت محظورة عند نقطة معيّنة،
بما في ذلك الأرقام المستورَدة جماعياً بدون جهة اتصال مطابقة.

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/compliance/suppression-list/export?format=csv&status=active" \
  -H "Authorization: Bearer $ORBIT_API_KEY" \
  -o suppression-list.csv
```

معاملات الاستعلام:

| المعامل       | النوع   | ملاحظات                                                                                 |
| ------------- | ------- | --------------------------------------------------------------------------------------- |
| `format`      | enum    | `csv` (افتراضي) أو `json`.                                                              |
| `channel`     | enum    | تقييد إلى نطاق قناة واحد.                                                               |
| `status`      | enum    | `active` (افتراضي — المجموعة التي تنفِّذها كل بوابة إرسال فعلياً)، `revoked`، أو `all`. |
| `from` / `to` | string  | نطاق تاريخي على `suppressed_at`. تاريخ مجرَّد `YYYY-MM-DD` أو تاريخ-وقت RFC-3339.       |
| `limit`       | integer | الصفوف المُضمَّنة (1–50,000؛ الافتراضي 50,000).                                         |

يحمل كل صف `suppression_id` و`channel` و`address` و`status` المشتقة
(`active` أو `revoked`) و`reason` و`source` و`contact_id` (فارغة
للعناوين المستورَدة جماعياً بدون جهة اتصال) و`notes` وطوابع
`suppressed_at` / `revoked_at` / `created_at` الزمنية.

الوصول مقيَّد بمفاتيح **owner** و**admin**، وكل تشغيل تصدير يُكتَب في
سجل التدقيق. عندما يتجاوز السجل 50,000 صف يحمل رد CSV ترويسة
`X-Export-Truncated: true` (ومكافئ JSON يعيِّن `truncated: true`) —
ضيِّق حسب القناة أو صدِّر نوافذ تاريخية متتالية لالتقاط الذِّيل.

***

## التحقق من نفاذ الحظر

ثِق لكن تحقَّق: بعد الاستيراد (أو أي حدث إلغاء اشتراك)، أكِّد أن
بوابة الإرسال تحصِّن العنوان فعلياً قبل تسليم القائمة إلى حملة.

1. **أرسِل رسالة اختبار إلى العنوان المحظور.** الإرسال المباشر عبر
   API إلى مستلم محظور يَفشَل تزامنياً مع HTTP 422 ورمز الخطأ
   `RECIPIENT_OPTED_OUT`. في [وضع sandbox](/sandbox/magic-numbers) لا
   تُلمَس أي شركة اتصالات ولا يُخصَم أي رصيد؛ وأي مستلم ينتهي بـ `8`
   يُحَال أيضاً إلى إيصال التسليم المحاكى `blocked`، وهي رؤية جانب
   شركة الاتصالات لنفس الحاجز.

   ```bash theme={null}
   curl -X POST https://api.orbit.devotel.io/api/v1/messages/sms \
     -H "X-API-Key: $ORBIT_SANDBOX_KEY" \
     -H "Content-Type: application/json" \
     -d '{"to": "+14155550101", "from": "+15005550101", "body": "gate check"}'
   ```

   ```json theme={null}
   {
     "error": {
       "code": "RECIPIENT_OPTED_OUT",
       "message": "Recipient has opted out of this channel"
     }
   }
   ```

   إرسال حملة إلى نفس العنوان يتصرّف بشكل مختلف تصميمياً: يُتخطَّى
   المستلم بصمت (`status: "skipped"`، `reason: "opted_out"`) حتى تستمر
   الدفعة — افحص تقرير الحملة لكل مستلم بدلاً من توقُّع خطأ.
2. **أكِّد أن الإدخال موجود على السجل.** صدِّر بـ
   [الاستعلام أعلاه](#تصدير-قائمة-الحظر) (`status=active` هو الافتراضي)
   وتحقّق من ظهور العنوان بنطاق `channel` المتوقَّع. التصدير هو مصدر
   الحقيقة الذي تقرأ منه كل بوابة إرسال — إذا كان الصف `active` هناك،
   فالحاجز قائم.

يجيب الفحصان عن سؤالين مختلفين: الخطوة 1 تُثبِت التنفيذ (انطلاق
البوابة)، والخطوة 2 تُثبِت النطاق (وجود الإدخال بالقناة التي قصدتها).

***

## إزالة الحظر (إعادة الاشتراك)

لإعادة عنوان ما، سجِّل اشتراكاً جديداً عبر
[Consent API](/compliance/consent-management) (`opt_in: true`). هذا
يلغي إدخال الحظر المطابق ويُزيل حاجز STOP. لا تُعِد مراسلة جهة اتصال
محظورة سابقاً أبداً دون حدث موافقة جديد وموثَّق.

**تأكيد أن الحاجز قد أُزيل.** صدِّر بـ `status=revoked` وابحَث عن
العنوان: الصف ما يزال موجوداً للتدقيق بوضع `status: revoked` وطابع
`revoked_at` زمني — سجل الحظر لا يُحذَف أبداً، بل يُلغَى فقط. ثم أرسِل
رسالة اختبار صغيرة إلى العنوان كما في
[التحقق من نفاذ الحظر](#التحقق-من-نفاذ-الحظر): نجاح الإرسال (بدون
`RECIPIENT_OPTED_OUT`) يؤكِّد أن البوابة لم تعد تنطلِق على السجل
الملغَى. وحتى يجتاز الفحصان، عامِل العنوان كأنه ما يزال محميًّا.

***

## مراجع ذات صلة

* [إدارة الموافقة](/compliance/consent-management) — تسجيل الموافقة
  لكل قناة والبحث عنها.
* [بوابات الإرسال](/compliance/send-gates) — ساعات الهدوء وDNC وRND
  وRMD والإيقاف الطارئ ومركز التفضيلات.
* [DSAR](/compliance/dsar) — كيف تصل طلبات `delete` / `opt_out` إلى
  الحظر.
* [مرجع API ← إلغاء الاشتراك](/api-reference/optouts) — مخططات نقاط
  نهاية إلغاء الاشتراك والحظر.
