جهات الاتصال
جهات الاتصال هم الأشخاص الذين تراسلهم مساحة العمل، مفتاحهم رقم الهاتف. والجزء المهم هو الاستيراد الجماعي: على أي مفتاح يزيل التكرار، وماذا يفعل حين يجد تطابقا.
النقاط
جهة الاتصال فريدة في مساحة العمل برقمها بعد تطبيعه إلى E.164. وحذفها حذف ناعم، وإنشاء الرقم نفسه أو استيراده بعدها يستعيدها بدل أن يُنشئ صفا ثانيا.
curl -X POST https://whats.azzamkh.sa/api/v1/contacts \
-H "Authorization: Bearer $WA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"phone": "+9665XXXXXXXX",
"name": "سارة الأحمد",
"email": "sara@example.com",
"locale": "ar",
"opt_in_status": "opted_in",
"tags": ["vip"],
"attributes": { "customer_id": "A-10428" }
}'
# 201 Created
# { "id": "cnt_...", "phone": "+9665XXXXXXXX", "source": "api", ... }
# The number already exists and is live:
# 409 { "error": { "code": "CONTACT_ALREADY_EXISTS",
# "details": { "contact_id": "cnt_..." } } }الحقول
| الحقل | النوع | مطلوب | الوصف |
|---|---|---|---|
| phone | string | مطلوب | الرقم. يُطبَّع إلى E.164 عند الكتابة، ولا يتغيّر بعدها. |
| name | string | اختياري | الاسم المعروض. |
| first_name, last_name | string | اختياري | الاسم الأول واسم العائلة. وإن لم يكن هناك اسم معروض رُكّب منهما. |
| string | اختياري | يُحوَّل إلى حروف صغيرة عند الكتابة. | |
| locale | string | اختياري | رمز لغة مثل ar أو en-GB. |
| notes | string | اختياري | نص حر. |
| opt_in_status | "unknown" | "opted_in" | "opted_out" | اختياري | موافقة التسويق. الافتراضي unknown. |
| attributes | object<string, string> | اختياري | بياناتك الخاصة كمفتاح وقيمة. المفاتيح بصيغة lower_snake_case؛ وعددها وطول القيمة كلاهما محدود. |
| tags | string[] | اختياري | أسماء الوسوم لا معرّفاتها. والأسماء المجهولة تُنشأ. |
| group_ids | string[] | اختياري | معرّفات المجموعات التي تُضاف إليها جهة الاتصال. |
| default_calling_code | string | اختياري | يُستخدم لتطبيع رقم مكتوب بالصيغة المحلية، مثل 966. |
null يمسح، والغياب يترك
عند التعديل تفرّق الحقول النصية القابلة للإفراغ بين الأمرين: إرسال null يمسح العمود، وإغفال الحقل يتركه كما هو. وإرسال tags أو group_ids يستبدل المجموعة كلها لا يضيف إليها.
المرشّحات
| المعامل | القيم | الوصف |
|---|---|---|
| q | string | بحث غير حساس للحالة في الاسم والبريد والرقم. والعبارة التي فيها ثلاثة أرقام فأكثر تطابق الرقم أيضا. |
| tag | tag_… | slug | معرّف وسم عام أو الـ slug الخاص به — كلاهما يعمل. |
| group_id | grp_… | جهات الاتصال في مجموعة واحدة. |
| opt_in_status | unknown, opted_in, opted_out | الترشيح بالموافقة. |
| source | manual, import, api, inbound, campaign, automation, system | كيف دخلت جهة الاتصال مساحة العمل. |
| limit, after | — | تقسيم بالـ cursor، الأحدث أولا. |
القيمة المجهولة في opt_in_status أو source تعطي 422 يسمّي المجموعة المسموحة، لا مرشّحا يُتجاهل.
المجموعات
المجموعة قائمة ثابتة. تُحرَّر العضوية جماعيا، ونقطة الإزالة تأخذ جسما في طلب DELETE — وهو أمر غير معتاد، لكنه ما يتيح لك إزالة ألف جهة اتصال في استدعاء واحد.
استيراد جدول
يصل الملف مرمّزا بـ base64 داخل JSON: لا توجد نقطة multipart. ويُقبل الاستيراد بـ 202 ويعمل في الخلفية، فاستعلم عن العملية لمعرفة التقدم والنتائج. وأرسل dry_run: true أولا — فهو يحلّل ويتحقق ويحصي دون أن يكتب شيئا، وهو السبيل الآمن الوحيد لمعرفة ماذا سيفعل الملف.
# The file travels base64-encoded inside JSON. There is no multipart route.
CONTENT=$(base64 < contacts.csv | tr -d '\n')
curl -X POST https://whats.azzamkh.sa/api/v1/contacts/import \
-H "Authorization: Bearer $WA_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"filename\": \"contacts.csv\",
\"format\": \"csv\",
\"content\": \"$CONTENT\",
\"dry_run\": true,
\"default_calling_code\": \"966\"
}"
# 202 Accepted — the import runs in the background. Poll it:
curl https://whats.azzamkh.sa/api/v1/contacts/imports/imp_... \
-H "Authorization: Bearer $WA_API_KEY"
# {
# "id": "imp_...",
# "status": "completed",
# "dry_run": true,
# "progress": { "total_rows": 1200, "processed_rows": 1200, "fraction": 1 },
# "result": { "created": 1140, "updated": 48, "duplicates": 52, "invalid": 8 },
# "errors": [
# { "row": 74, "column": "phone", "reason": "invalid_phone", "value": "05x1234" }
# ]
# }الحدود
| الحد | القيمة |
|---|---|
| الصيغ | csv, xlsx |
| حجم الملف بعد فك الترميز | 5 MiB |
| الصفوف في العملية الواحدة | 50 000 |
| أخطاء الصفوف المسرودة | 100 |
| مدة حفظ الملف | 7 d |
تُكتشف الصيغة من بايتات الملف الأولى لا من امتداده، فالجدول المحفوظ بلاحقة خاطئة يُستورد صحيحا. والصيغة القديمة .xls غير مدعومة.
إزالة التكرار
تحدث إزالة التكرار مرتين، على مفتاحين، وسلوكهما مختلف. وكلاهما يعتمد رقم الهاتف بعد تطبيعه.
- داخل الملف الواحد تُدمج الأرقام المكررة لا تُسقَط: القيم غير الفارغة الأحدث تغلب، وتُوحَّد الوسوم، وتُدمج الخصائص. ويُحسب الصف مكررا لكن التعديل يُحفظ.
- مقابل مساحة العمل، الرقم المطابق يُحدَّث ولا يُتخطى أبدا. فالاستيراد عملية upsert.
- لا يكتب فوق القيمة إلا قيمة غير فارغة. فالعمود الفارغ لا يمحو بيانات عندك أصلا — وهذا ما يجعل استيراد جدول ناقص آمنا.
- جهة اتصال محذوفة حذفا ناعما يظهر رقمها في الملف تُستعاد.
الأعداد الأربعة لا تُجمع — عمدا
total_rows = created + duplicates + invalid. أما updated فهو عدّ فرعي داخل duplicates لا فئة رابعة، فجمع الأربعة يحسب كل صف محدَّث مرتين.
ربط الأعمدة
تُطابق العناوين مع مرادفات إنجليزية وعربية، فملف فيه عمود عنوانه رقم الجوال يُربط بحقل الهاتف بلا إعداد. وأرسل mapping صريحا لتتجاوز ذلك. والعمود الذي لا يُربط بشيء يصير خاصية مخصصة بدل أن يُهمل.
| reason | الوصف |
|---|---|
| missing_phone | الصف بلا رقم هاتف. |
| invalid_phone | تعذّر تطبيع الرقم إلى E.164. |
| duplicate_in_file | ظهر الرقم قبل ذلك في الملف نفسه. |
| row_too_wide | أعمدة الصف أكثر من أعمدة العنوان. |
| unmapped_header | تعذّر ربط عنوان وتعذّر جعله خاصية. |