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

# قراءة المستندات

> POST /v1/kyc/extract: من 1 إلى 5 ملفات كمدخلات، والمخرجات هي الحقول وفحوص كل مستند وملف حالة.

يقرأ `POST /v1/kyc/extract` ملفات خطوة رفع واحدة ويعيد الحقول التي عثر عليها، والفحوص الخاصة بكل مستند، ومع `reference` يعيد معرّف ملف حالة (case) ومعرّفات المستندات. النطاق (scope): `kyc:extract`. هذا هو الاستدعاء الوحيد الذي يستخدم نموذج الرؤية، ولذلك هو الوحيد الذي يُحتسب ضمن حصة القراءات الشهرية.

## الطلب

نموذج متعدد الأجزاء (`multipart/form-data`). الحقل `files` وحده مطلوب.

| الحقل | النوع | القيمة الافتراضية | القاعدة |
| - | - | - | - |
| `files` | ملف، قابل للتكرار | لا شيء | من 1 إلى 5 ملفات. JPEG أو PNG أو WebP أو TIFF أو PDF. |
| `doc_type` | سلسلة نصية | لا شيء | تلميح للقارئ عن نوع المستند، مثل `payslip` أو `national_id`. يسري على كل ملف في الاستدعاء. |
| `step_key` | سلسلة نصية | لا شيء | اسم ثابت لخطوة الرفع لديكم، مثل `photo_id`. يحدد أنواع المستندات التي تقبلها الخطوة. انظر [أنواع المستندات والحقول](/ar/guides/document-types). |
| `reference` | سلسلة نصية | لا شيء | معرّفكم للعميل، من 1 إلى 64 حرفًا من `A-Z a-z 0-9 _ . : -`. يحفظ النتيجة في ملف حالة. |
| `environment` | سلسلة نصية | `sandbox` | `sandbox` أو `production`. أي قيمة أخرى تُرجع 422. |
| `subject` | سلسلة نصية | لا شيء | اسم العميل لملف الحالة، حتى 255 حرفًا. |
| `kind` | سلسلة نصية | لا شيء | `individual` أو `corporation` أو `partnership` أو `charitable_org` أو `trust` أو `estate`، أو أحد مرادفاتها. يختار عتبات الأفراد أو الكيانات لفحوص المستندات. القيم غير المعروفة تُعامل على أنها `individual`. |

أرسلوا نوع مستند واحدًا في كل استدعاء. يسري `doc_type` و`step_key` على جميع الملفات في الاستدعاء، والحالة المعتادة لملفين هي وجه البطاقة وظهرها.

## قواعد الملفات

| القاعدة | القيمة | مصدرها |
| - | - | - |
| عدد الملفات في الاستدعاء | من 1 إلى 5 | مثبّت في الموجّه (router). وإلا 400 `Send between 1 and 5 files.` |
| الصيغ | `image/jpeg`, `image/png`, `image/webp`, `image/tiff`, `application/pdf` | أداة التحقق من الرفع. يُقبل `image/jpg` على أنه JPEG. |
| حجم الملف | 30 ميغابايت افتراضيًا | الإعداد `MAX_UPLOAD_MB`. عند تجاوزه: 413 `File too large. Maximum allowed size is 30 MB.` |
| حجم الصورة | 50 ميغابكسل (7000 x 7000) افتراضيًا | يُقرأ من ترويسة الصورة، لذلك قد يُرفض ملف صغير الحجم. 413. |
| فحص المحتوى | يجب أن تطابق البايتات الأولى النوع المصرّح به | وإلا 400 `File content does not match declared MIME type.` يجوز أن يسبق `%PDF-` في ملف PDF مقدمة بطول 1 كيلوبايت كحد أقصى. |
| صفحات PDF | لا يُطبَّق حد لعدد الصفحات على هذه النقطة | فحص عدد الصفحات لا يُستدعى من `/extract`. |

يُؤخذ نوع المحتوى من ترويسة جزء multipart لديكم، لا من اسم الملف. أرسلوا `Content-Type` الصحيح لكل جزء ملف. تضبطه معظم مكتبات HTTP و`curl -F` من الامتداد.

## ما يحدث عند الاستدعاء

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant App as Your server
    participant API as Sahl Partner API
    participant Reader as Vision model
    App->>API: POST /v1/kyc/extract
    API->>API: validate reference, environment, subject, file count
    API->>API: validate each file (size, type, first bytes, pixels)
    API->>API: load the workspace policy
    API->>API: reserve N reads for the month (429 if over)
    API->>Reader: one read per file
    Reader-->>API: JSON of fields and a document type
    API->>API: normalise values, run document checks
    API-->>App: 200 fields, documents, checks
```

الترتيب مهم في موضعين. أخطاء التحقق (400 و413 و422) تحدث قبل حجز القراءة، فلا تكلّف شيئًا. ويحدث الحجز قبل تشغيل النموذج، فالقراءة التي تفشل بعد ذلك تُحتسب مع ذلك.

## الاستجابة

```json theme={null}
{
  "fields": {
    "first_name": "Test",
    "last_name": "Client",
    "document_holder_name": "Test Client",
    "employer_name": "Test Employer SARL",
    "occupation": "Analyst",
    "document_date": "2026-09-30"
  },
  "documents": [
    {
      "filename": "payslip-test.pdf",
      "doc_type": "payslip",
      "step_hint": "payslip",
      "step_key": null,
      "fields": {
        "first_name": "Test",
        "last_name": "Client",
        "document_holder_name": "Test Client",
        "employer_name": "Test Employer SARL",
        "occupation": "Analyst",
        "document_date": "2026-09-30"
      },
      "meta_created": "2026-10-01",
      "meta_provenance": { "producer": "Example Payroll 4.2", "revisions": 1 },
      "mapped": 6,
      "notes": [],
      "document_id": "22222222-2222-4222-8222-222222222222"
    }
  ],
  "field_count": 6,
  "checks": [],
  "reader_unavailable": false,
  "policy": { "id": null, "version": 0, "source": "legacy", "regime": "none", "regulator": null, "purpose": "onboarding", "overrides_refused": [] },
  "case_id": "00000000-0000-4000-8000-000000000001",
  "document_ids": ["22222222-2222-4222-8222-222222222222"]
}
```

القيم أعلاه وهمية. تعتمد الحقول التي تظهر على الملف الذي ترسلونه.

### مفاتيح المستوى الأعلى

| المفتاح | النوع | المعنى |
| - | - | - |
| `fields` | كائن من السلاسل النصية | جميع حقول الاستدعاء بعد الدمج. إذا وُجد المفتاح في عدة ملفات فالقيمة غير الفارغة الأولى هي المعتمدة، بحسب ترتيب `files`. أرسلوا وثيقة الهوية الأقوى أولًا. |
| `documents` | مصفوفة | عنصر واحد لكل ملف، بترتيب الإرسال. |
| `field_count` | عدد صحيح | عدد المفاتيح في `fields`. |
| `checks` | مصفوفة | الفحوص الخاصة بكل مستند في جميع الملفات، في قائمة واحدة. |
| `reader_unavailable` | قيمة منطقية | تكون true عندما لم يُقرأ ملف واحد على الأقل. |
| `policy` | كائن | سياسة مساحة العمل التي جرى الاستدعاء وفقها. |
| `case_id` | uuid | مع `reference` فقط. |
| `document_ids` | مصفوفة من uuid | مع `reference` فقط. بنفس ترتيب `documents`. |

### مفاتيح كل عنصر في `documents[]`

| المفتاح | النوع | المعنى |
| - | - | - |
| `filename` | سلسلة نصية | الاسم الذي أرسلتموه. |
| `doc_type` | سلسلة نصية أو null | ما يقوله القارئ عن نوع المستند. انظر [القائمة](/ar/guides/document-types). تكون null إذا تعذّر عليه تسميته. |
| `step_hint` | سلسلة نصية أو null | قيمة `doc_type` التي أرسلتموها. |
| `step_key` | سلسلة نصية أو null | قيمة `step_key` التي أرسلتموها. |
| `fields` | كائن | الحقول المقروءة من هذا الملف وحده. |
| `meta_created` | سلسلة نصية أو null | تاريخ إنشاء الملف بصيغة `YYYY-MM-DD`: قيمة `CreationDate` في PDF، أو تاريخ EXIF للصورة. |
| `meta_provenance` | كائن | ما يذكره الملف عن كيفية إنشائه. فارغ عندما لا يُعرف شيء. |
| `mapped` | عدد صحيح | عدد الحقول المقروءة من هذا الملف. |
| `notes` | مصفوفة من السلاسل النصية | تصحيحات أجراها الخادم على إجابة القارئ، مع السبب. |
| `document_id` | uuid | مع `reference` فقط. |

مفاتيح `meta_provenance`: لملف PDF، `producer` و`creator` (البرنامج، حتى 200 حرف)، و`modified` (قيمة `ModDate` بصيغة `YYYY-MM-DD`، عندما تختلف عن تاريخ الإنشاء)، و`revisions` (عدد مرات حفظ الملف تزايديًا). وللصورة، `creator` (وسم EXIF Software) و`camera` (وسم EXIF Make). غياب كتلة EXIF لا يُبلَّغ عنه، لأن WhatsApp ومعظم المتصفحات تزيلها.

### قيم الحقول

كل قيمة في `fields` سلسلة نصية. يُطلب من القارئ حذف الحقل الذي لا يستطيع قراءته، فغياب المفتاح يعني "لم يُقرأ" وليس "فارغ" أبدًا. ثم يوحّد الخادم القيم:

| النوع | القاعدة |
| - | - |
| التواريخ (`date_of_birth`, `id_expiry`, `incorporation_date`, `document_date`) | `YYYY-MM-DD`. تُحوَّل التواريخ التي يسبق فيها اليوم (`DD/MM/YYYY`) والأرقام الهندية الشرقية. يُحوَّل التاريخ الهجري (السنة من 1343 إلى 1500، أو المعلَّم بـ `AH`) إلى ميلادي. إذا تعذّر التحويل يُعاد النص كما هو مطبوع. |
| `province`, `id_province` | رمز من حرفين، مثل `QC` و`ON` و`NY`. |
| `country`, `citizenship` | رمز ISO من حرفين، مثل `MA` و`CA` و`US`. (`id_country` رمز من حرفين يُطلب من القارئ؛ ولا يعيد الخادم كتابته.) |
| الأرقام (`sin`, `ssn`, `bank_number`, `bank_transit`, `bank_routing`, `bank_account`, `business_number`, `ice`, `if_number`, `iban`) | أرقام وحروف فقط، دون مسافات أو شرطات. |
| المبالغ (`annual_income`, `net_liquid_assets`, `net_fixed_assets`, `total_net_worth`) | يُطلب من القارئ أرقام فقط، دون رمز أو فاصل؛ ولا يعيد الخادم تنسيقها. تُقرأ فقط من مستند يذكر المبلغ. يعود المبلغ بالدرهم (MAD) أرقامًا دون العملة. |
| `sex` | `M` أو `F`. |
| أي قيمة | حتى 500 حرف. تتحول الأرقام الهندية الشرقية والفارسية إلى 0 حتى 9. |

التصحيحات التي يجريها الخادم بعد القراءة، وكل منها مدرج في `notes`:

* لا تُحفظ البيانات المصرفية إلا إذا كان المستند مستندًا مصرفيًا (شيك ملغى، كشف حساب، رسالة من البنك، RIB). وتُزال من أي مستند آخر، لأن رقم الحساب في فاتورة مرافق أو IBAN في فاتورة ليس للعميل.
* `bank_number` و`bank_transit` رمزان كنديان. يُسقطان إذا كان المستند من بلد آخر، أو إذا كان الطول خاطئًا (3 أرقام و5 أرقام). ويُنقل `bank_number` المكوّن من 9 أرقام إلى `bank_routing`، لأن 9 أرقام تمثل رقم توجيه أمريكيًا.
* الاسم الذي يُقرأ على أنه اسم أحد الوالدين في البطاقة المغربية (`... ben ...` و`fils de` و`bent`) يُسقط من `first_name` و`last_name` و`document_holder_name`.
* في الفاتورة، لا يُدمج الاسم القانوني للمورّد وعنوانه وأرقام تسجيله في `fields` ما لم يكن المستلم هو الكيان نفسه.
* يبقى `specimen_markings` في عنصر المستند ولا يُدمج في `fields` أبدًا.

## الثقة

لا تعيد الواجهة درجة ثقة لكل حقل ولا درجة لكل مستند. يعطي القارئ قيمًا لا احتمالات. لا تبحثوا عن مفتاح ثقة.

ما زال بإمكانكم الحكم على القراءة:

| الإشارة | أين | ما العمل |
| - | - | - |
| مفتاح غائب من `fields` | `documents[].fields` | لم يعثر القارئ عليه. اطلبوه من الشخص أو أعيدوا المسح. |
| فحص `legible:` بدرجة `warning` | `checks` | بعض الحقول المتوقعة مفقودة. تسردها التفاصيل. |
| فحص `legible:` بدرجة `critical` | `checks` | لم يُقرأ أي من الحقول المتوقعة. اعتبروا الملف غير مقروء. |
| فشل فحص `doctype:` | `checks` | يقول القارئ إن المستند من نوع آخر غير ما تقبله الخطوة. |
| `reader_unavailable: true` | المستوى الأعلى | لم يُقرأ الملف إطلاقًا. أعيدوا المحاولة لاحقًا. |
| فحوص الصيغة مثل `format:cin:` أو `mrz:` | `checks` | الرقم لا يطابق صيغته. غالبًا خطأ في القراءة. |

تعرض وحدة التحكم (console) قيمة ثابتة 0.9 لكل حقل يعيده القارئ. هي وسم يعني "قرأه النموذج ولم يُراجَع بعد"، وليست قياسًا، ويبقى التحقق من الحقل `pending` إلى أن يراجعه شخص.

## `reader_unavailable`

تعني `reader_unavailable: true` أن ملفًا واحدًا على الأقل لم يُقرأ: لا توجد بيانات اعتماد من جانب Sahl، أو تجاوز حصة أو مهلة، أو إجابة تعذّر تحليلها. ولا تعني أن المستند كان فارغًا. المستند الفارغ أو المقصوص يعيد `reader_unavailable: false` مع حقول قليلة أو دون حقول.

يعيد الاستدعاء 200 مع ذلك. وتُحتسب القراءة. أعيدوا محاولة الملف لاحقًا، وإن استمر ذلك فاذكروا ترويسة `X-Request-ID` لـ Sahl.

مع `reference` يُحفظ كل ملف في ملف الحالة بوضع تشاهدونه في وحدة التحكم ضمن Documents:

| الوضع | متى |
| - | - |
| `completed` | قُرئت الحقول ولم يفشل أي فحص. |
| `completed_with_warnings` | قُرئت الحقول وفشل فحص واحد على الأقل (critical أو warning). |
| `review_required` | لم يُقرأ أي حقل وكان القارئ متاحًا: الصفحة لا تحوي شيئًا يعرفه. |
| `failed` | لم يُقرأ أي حقل وكان القارئ غير متاح (رمز الخطأ `reader_unavailable`). |

## فحوص المستندات في الإجابة

يحصل كل ملف على الفحوص التي تناسب نوعه. وهي نفسها الفحوص التي يكررها `/verify` على المدخلات التي تعيدونها. القائمة الكاملة بمعانيها في [التحقق من ملف تعريف](/ar/guides/verification).

باختصار: المستند من النوع الذي تتوقعه الخطوة (`doctype:`)، وقُرئت حقوله الأساسية (`legible:`)، والهوية غير منتهية (`expiry:`) أو على وشك الانتهاء (`expiry_soon:`)، وصاحبها بعمر 18 سنة أو أكثر (`adult:`)، وأرقام التحقق في MRZ لجواز السفر أو بطاقة الهوية صحيحة (`mrz:`)، وإثبات العنوان حديث (`recency:`)، ولم يُعد حفظ الملف من محرر (`provenance:`)، والمستند ليس نموذجًا أو عينة (`authenticity:specimen:`).

قسيمة الراتب (payslip) لا تحصل على أي فحص مستند. يحمل عنصرها الحقول فقط.

## أمثلة

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://app.sahlfinancial.com/api/v1/kyc/extract \
    -H "Authorization: Bearer $SAHL_API_KEY" \
    -F "files=@payslip-test.pdf;type=application/pdf" \
    -F "doc_type=payslip" \
    -F "reference=client-0001" \
    -F "environment=sandbox" \
    -F "subject=Test Client" \
    -F "kind=individual"
  ```

  ```javascript JavaScript theme={null}
  import { readFile } from "node:fs/promises";

  const form = new FormData();
  form.append("files", new Blob([await readFile("payslip-test.pdf")], { type: "application/pdf" }), "payslip-test.pdf");
  form.append("doc_type", "payslip");
  form.append("reference", "client-0001");
  form.append("environment", "sandbox");
  form.append("subject", "Test Client");
  form.append("kind", "individual");

  const res = await fetch("https://app.sahlfinancial.com/api/v1/kyc/extract", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.SAHL_API_KEY}` },
    body: form,
  });
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  const extracted = await res.json();
  console.log(extracted.field_count, extracted.reader_unavailable);
  ```

  ```python Python theme={null}
  import os, requests

  with open("payslip-test.pdf", "rb") as f:
      res = requests.post(
          "https://app.sahlfinancial.com/api/v1/kyc/extract",
          headers={"Authorization": f"Bearer {os.environ['SAHL_API_KEY']}"},
          files=[("files", ("payslip-test.pdf", f, "application/pdf"))],
          data={
              "doc_type": "payslip",
              "reference": "client-0001",
              "environment": "sandbox",
              "subject": "Test Client",
              "kind": "individual",
          },
          timeout=120,
      )
  res.raise_for_status()
  extracted = res.json()
  print(extracted["field_count"], extracted["reader_unavailable"])
  ```
</CodeGroup>

ملفان لمستند واحد (وجه بطاقة الهوية وظهرها):

```bash theme={null}
curl -X POST https://app.sahlfinancial.com/api/v1/kyc/extract \
  -H "Authorization: Bearer $SAHL_API_KEY" \
  -F "files=@id-front-test.jpg" -F "files=@id-back-test.jpg" \
  -F "doc_type=national_id" -F "step_key=photo_id" \
  -F "reference=client-0001" -F "environment=sandbox"
```

## الحدود والاحتفاظ في الشيفرة

| البند | القيمة |
| - | - |
| القراءات في الشهر | 2,000 افتراضيًا، لكل مساحة عمل، في الشهر الميلادي (UTC). بعد تجاوزها: 429 `kyc_extract_cap_reached` مع `used` و`limit`. |
| متى تُحتسب | قبل تشغيل النموذج. القراءة الفاشلة تُحتسب. الاستدعاء المرفوض (400 و413 و422) لا يُحتسب. |
| التزامن | يُزاد العداد في تعليمة واحدة، فلا يمكن للاستدعاءات المتزامنة تجاوز الحد. |
| حد المعدل | 100 طلب في الدقيقة لكل عنوان IP للعميل على هذه المسارات. |
| دون `reference` | لا يُحفظ شيء في مساحة عملكم. تُعاد الحقول المقروءة في الاستجابة فقط. |
| مع `reference` | يُخزَّن الملف والحقول المقروءة والفحوص في ملف الحالة لديكم، حيث يراها موظفوكم ضمن Cases وDocuments. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.