تخطّ إلى المحتوى الرئيسي

Imports API

استيراد جماعي لجهات الاتصال أو الأحداث أو سجلات الكيانات المخصّصة من ملف CSV أو JSON.

جميع نقاط النهاية في هذه الصفحة نسبية إلى العنوان الأساسي: https://api-eu1.joryio.com/api - راجع نظرة عامة على API.

نظرة عامة​

الاستيراد ثلاث طلبات، لا طلب واحد. تطلب موقعاً للرفع، ترفع الملف مباشرة إلى التخزين، ثم تخبر Joryio بمعالجته:

  1. POST /users/import/upload-url - يعيد رابطاً موقّعاً مسبقاً وfileKey الذي يعرّف ملفك.
  2. PUT للملف إلى ذلك الرابط - مباشرة إلى التخزين، فلا يمر ملف بحجم 200 ميجابايت عبر الـ API.
  3. POST /users/import - ينشئ المهمة. يستجيب فوراً، وتتم معالجة الملف في الخلفية.
  4. GET /users/import - لمتابعة التقدّم والأعداد النهائية.

نجاح الخطوة 2 لا يقول شيئاً عن الخطوة 3. الرفع مجرد ملف يصل إلى مساحة تخزين، أما التحقق فيحدث عند إنشاء المهمة ثم لكل صف أثناء تنفيذها.

المصادقة​

Authorization: Bearer YOUR_API_KEY

كل نقاط النهاية هنا تتطلب users:write. أما تعيين overrideConsent فيتطلب إضافةً compliance:write - وusers:write وحدها تعيد 403 ولا يبدأ الاستيراد.

1. طلب رابط رفع​

POST /users/import/upload-url
Content-Type: application/json

{
"filename": "customers-2026-08.csv"
}
{
"success": true,
"fileKey": "imports/<organizationId>/<workspaceId>/customers-2026-08.csv",
"uploadUrl": "https://storage.example.com/..."
}

الـ fileKey محصور بمؤسستك ومساحة عملك. أعده كما هو تماماً في الخطوة 3، فالمفتاح خارج بادئتك الخاصة يُرفض بـ 400.

2. رفع الملف​

curl -X PUT --upload-file customers-2026-08.csv "$UPLOAD_URL"

الاستجابة 200 هنا تعني أن البايتات مخزّنة، ولا تعني أن الاستيراد سينجح.

3. إنشاء مهمة الاستيراد​

POST /users/import
Content-Type: application/json

{
"format": "csv",
"fileKey": "imports/<organizationId>/<workspaceId>/customers-2026-08.csv",
"importType": "users",
"originalFilename": "customers-2026-08.csv",
"fieldMapping": {
"Email Address": "email",
"First Name": "firstName",
"Loyalty Tier": "attributes.loyaltyTier"
}
}
الحقلالنوعملاحظات
formatcsv | jsonمطلوب
fileKeystringمطلوب. تماماً ما أعادته الخطوة 1
importTypeusers | events | entitiesالافتراضي users
originalFilenamestring?يظهر في سجل الاستيراد
fieldMappingobject?عمود المصدر ← مسار الحقل الهدف. اتركه ليُكتشف من صف العناوين
overrideConsentboolean?انظر أدناه. يتطلب compliance:write
smsAlreadyConfirmedboolean?للتأكيد المزدوج لاشتراك SMS فقط. انظر أدناه. يتطلب compliance:write
maxBackdatingDaysint?من 1 إلى 3650. إلى أي مدى يمكن تأريخ حدث مستورد في الماضي
entityNamestring?مطلوب عندما يكون importType هو entities

الحقول غير المعروفة تُرفض بـ 400 بدل تجاهلها، فيفشل الخطأ الإملائي في اسم حقل بشكل واضح بدل ألّا يفعل شيئاً بصمت.

fieldMapping يحتوي أعمدة، وأعمدة فقط​

كل مفتاح في fieldMapping هو اسم أحد أعمدتك أنت. خيارات الاستيراد حقول منفصلة ومحدّدة النوع - overrideConsent وentityName وmaxBackdatingDays - وليست أبداً مفاتيح في هذه الخريطة.

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

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

بدون fieldMapping، تُربط الكتابات الشائعة للعناوين بالحقول القياسية - مثل first_name أو First Name أو given_name إلى attributes.firstName، وsurname أو last_name إلى attributes.lastName، وe-mail إلى email، وmobile إلى phone؛ ويصبح كل عمود آخر سمة مخصصة باسمه. القائمة الكاملة في الاستيراد المجمع. لا يُقسَّم full_name. يمكن أن تكون ملفات CSV مفصولة بفواصل أو فواصل منقوطة أو علامات جدولة - ويُحدَّد الفاصل من صف العناوين.

ملفات JSON​

كلا الشكلين مقبول:

[ { "email": "[email protected]" }, { "email": "[email protected]" } ]
{ "users": [ { "email": "[email protected]" } ] }

overrideConsent​

افتراضياً يستطيع الاستيراد أن يمنع لا أن يمنح: صف unsubscribed في ملفك يلغي اشتراك جهة الاتصال، بينما صف subscribed لا يعيد اشتراك من ألغى اشتراكه سابقاً.

القيمة overrideConsent: true تعامل الملف كمرجع في الاتجاهين. هذا إجراء امتثال، لذا يتطلب compliance:write فوق users:write.

لاحظ اللاتماثل: إلغاءات الاشتراك تُطبَّق في الحالتين. لا تحتاج هذه الراية لمزامنة الاستبعادات من نظام CRM، بل فقط لإعادة اشتراك من ألغى اشتراكه.

يجب أن تكون القيمة قيمة منطقية في JSON. النص أو 1 أو "yes" تُرفض بـ 400 بدل قراءتها ضمنياً كـ false - أن تُخبَر أفضل من أن تصدّق أن تجاوزاً جرى ولم يجرِ.

حتى مع overrideConsent: true، تنطبق إعادة الاشتراك فقط على جهة الاتصال في الصف - وتبقى الجهات الأخرى بالبريد أو الهاتف نفسه ملغاة - ولا يُتجاوز إلغاء STOP إلا مع العلامة (ويُسجَّل في سجل جهة الاتصال مع مهمة الاستيراد)، ولا يرفع الاستيراد أبداً ارتداداً دائماً أو شكوى بريد عشوائي أو حظراً على مستوى المؤسسة. ومن دونها تُتجاهل قيمة subscribed لجهة اتصال محظورة. وتصل قيمة unsubscribed إلى كل جهة اتصال في مساحة العمل بالبريد أو الهاتف نفسه. جهات الاتصال التي تشترك في بريد إلكتروني أو هاتف.

تحمل المهمة المنتهية consentSummary:

{ "overridden": 12, "overriddenSmsStop": 3, "keptUnsubscribed": 40, "keptDeliverability": 1, "keptOrganization": 0 }

يعدّ overridden إعادات الاشتراك التي تمت مع overrideConsent، وoverriddenSmsStop عدد ما كان منها جهات اتصال SMS أرسلت STOP (قد يواصل مزوّد SMS حظر الرسائل إلى أن ترسل START)؛ ويعدّ keptUnsubscribed قيم subscribed التي تم تجاهلها لأن العلامة كانت معطلة؛ ويعدّ keptDeliverability وkeptOrganization ما لا يحق لأي استيراد رفعه. تكون قيمة consentSummary هي null للاستيرادات التي انتهت قبل وجوده.

smsAlreadyConfirmed​

لا يهم إلا عندما يكون التأكيد المزدوج لاشتراك SMS مفعلا في مساحة العمل. عندها يُحفظ كل اشتراك SMS جديد في الملف (جهة اتصال بلا حالة SMS) بالحالة pending ويتلقى رسالة "Reply YES" واحدة خلال دقيقة تقريبا؛ ويعدّها consentSummary.awaitingSmsConfirmation. القيمة smsAlreadyConfirmed: true تعني أن لديك إثباتا بأن هؤلاء الأشخاص أكدوا اشتراكهم في SMS من قبل: فيُحفظون subscribed دون رسالة. إنها إقرار امتثال، لذا تتطلب compliance:write مثل overrideConsent.

مع overrideConsent: true (وتفعيل التأكيد المزدوج لاشتراك SMS)، تُعامل جهة الاتصال في الملف التي ألغت اشتراكها في SMS كاشتراك جديد: pending، ورسالة تأكيد واحدة، دون تذكير، ولا تصبح مشتركة إلا بعد YES، وتعود unsubscribed إن لم ترد. بدونه تبقى جهات الاتصال الملغاة دون تغيير ولا تتلقى رسالة. أما smsAlreadyConfirmed: true فيجعلها مشتركة بدلا من ذلك (مع overrideConsent). ولا يؤكد أي ملف جهة اتصال حالتها pending.

4. متابعة التقدّم​

GET /users/import

يعيد سجل استيراد مساحة العمل، الأحدث أولاً، مع status وtotalRows وprocessedRows وcreatedCount وupdatedCount وskippedCount وfailedCount، وعند فشل صفوف عيّنة محدودة من الأخطاء مع أرقام صفوفها في المصدر. يعدّ matchedSeveralCount الصفوف التي تحمل بريدًا إلكترونيًا دون externalId بينما تتشارك عدة جهات اتصال هذا البريد: حدّث كل صف منها جهة الاتصال الأحدث نشاطًا فقط، ووصل إلغاء الاشتراك فيه إليها جميعًا، ويدرجه تقرير الأخطاء بوصفه notice. انظر عندما تتشارك عدة جهات اتصال بريدًا إلكترونيًا واحدًا؛ أضف externalId إلى صفوفك لمطابقة جهة اتصال واحدة بدقة.

تنتقل status عبر pending ← processing ← completed أو failed.

5. عرض عمليات الاستيراد التي يمكن لشريحة استهدافها​

GET /users/import/sources?limit=100

يتطلب users:read. يعيد عمليات استيراد جهات الاتصال (users) المكتملة في مساحة العمل، الأحدث أولًا - وهي القائمة التي يختار منها شرط الشريحة جاء من استيراد:

{
"imports": [
{
"id": "6f1c2a9e-4b7d-4c1e-9a53-2f0d8e7b1c44",
"fileName": "customers-2026-08.csv",
"status": "completed",
"createdAt": "2026-10-08T09:00:00.000Z",
"completedAt": "2026-10-08T09:05:12.000Z",
"rowCount": 1200,
"createdCount": 1000,
"updatedCount": 150
}
]
}

القيمة الافتراضية لـ limit هي 100 والحد الأقصى 200. يعيد ids (مفصولة بفواصل، حتى 100) عمليات الاستيراد هذه بالضبط مهما كان عمرها - لتسمية عمليات الاستيراد التي يشير إليها شرط محفوظ. قيمة status هي completed أو expired (اكتملت؛ وحُذف تقرير أخطائها) أو failed - تظهر عملية الاستيراد الفاشلة فقط إذا وصلت إلى معالجة صفوف، لأن جهات الاتصال التي بلغتها موسومة. لا تظهر عمليات استيراد الأحداث والكيانات.

كل جهة اتصال ينشئها أو يحدّثها استيراد جهات اتصال تُوسَم بـ id العملية، لذلك يمكن لشريحة استهداف العملية:

{ "type": "contact_import", "operator": "any_of", "value": ["6f1c2a9e-4b7d-4c1e-9a53-2f0d8e7b1c44"] }

راجع الفلتر contact_import في Segments API. جهات الاتصال المستوردة قبل إطلاق هذه الميزة لا تحمل وسمًا.

استيراد واحد في كل مرة​

يمكن لمساحة العمل أن تملك استيراداً واحداً بحالة pending أو processing. الطلب الثاني أثناء وجود استيراد نشط يعيد:

{ "statusCode": 400, "message": "An import is already in progress for this workspace. Please wait for it to complete." }

تابع الخطوة 4 حتى ينتهي الأول بدل إعادة المحاولة فوراً.

الأخطاء​

الحالةالمعنى
400حقل غير معروف، أو قيمة غير صالحة لـ overrideConsent، أو fileKey خارج بادئتك، أو استيراد entities بلا entityName، أو استيراد قيد التشغيل بالفعل
403طُلب overrideConsent بدون compliance:write
403طُلب smsAlreadyConfirmed بدون compliance:write
404الـ fileKey لا يشير إلى أي ملف مرفوع - تُنشأ المهمة ثم تفشل بـ "The specified key does not exist"

الصفوف التي تفشل منفردة لا تُفشل المهمة. تُحتسب في failedCount وتظهر في تقرير الأخطاء، فملف فيه ثلاثة صفوف خاطئة من خمسين ألفاً يستورد الـ 49,997 الباقية.