REST API على مستوى المؤسسات وwebhooks في الوقت الفعلي تمكّن التكامل السلس بين TenancyDesk وأنظمة عملك الحالية. قم ببناء سير عمل مخصص، أتمتة مزامنة البيانات، وإنشاء تكاملات قوية.
نظرة عامة على API
البنية المعمارية
RESTful API يتبع معايير الصناعة:
- URLs قائمة على الموارد (
/deals,/parties,/documents) - أساليب HTTP: GET, POST, PUT, PATCH, DELETE
- أجسام طلبات/استجابات JSON
- رموز حالة HTTP القياسية
- مواصفات OpenAPI 3.0 متاحة
Base URL: https://api.tenancydesk.com/v1/
وثائق API: https://docs.tenancydesk.com/api (Swagger UI تفاعلي)
المصادقة
API Keys (موصى به)
توليد مفتاح API:
- الإعدادات → التكاملات → API Keys
- انقر توليد مفتاح API جديد
- الاسم: "Production Integration" (وصفي)
- النطاقات: حدد الأذونات (read:deals, write:parties، إلخ)
- انسخ المفتاح فوراً (يُعرض مرة واحدة فقط)
عنوان المصادقة:
curl -H "Authorization: Bearer sk_live_abc123xyz..." \
https://api.tenancydesk.com/v1/deals
الأمان:
- تخزين آمن (متغيرات البيئة، ليس الكود)
- تدوير كل 90 يوماً
- استخدم مفاتيح مختلفة لـ dev/staging/prod
- إلغاء فوري إذا تم الاختراق
JWT Bearer Tokens (OAuth 2.0 - Enterprise)
لعمليات سياق المستخدم:
- احصل على رمز الوصول عبر OAuth flow
- ضمّنه في عنوان Authorization
- انتهاء الرمز: ساعة واحدة (رمز التحديث: 7 أيام)
استخدم عندما: بناء تطبيقات حيث يصادق المستخدمون بحسابات TenancyDesk الخاصة بهم
نقاط API الأساسية
Deals API
قائمة جميع الصفقات:
GET /v1/deals?status=active&limit=50&offset=0
Response 200:
{
"data": [
{
"id": "deal_abc123",
"property_address": "Unit 1205, Churchill Tower, Business Bay",
"tenant_name": "أحمد محمد",
"annual_rent": 85000,
"status": "active",
"ejari_number": "12345678",
"created_at": "2025-01-15T10:30:00Z"
}
],
"pagination": {
"total": 127,
"limit": 50,
"offset": 0,
"has_more": true
}
}
إنشاء صفقة:
POST /v1/deals
Content-Type: application/json
{
"property_address": "Unit 1205, Churchill Tower",
"tenant": {
"name": "أحمد محمد",
"emirates_id": "784-1988-1234567-1",
"mobile": "+971501234567"
},
"annual_rent": 85000,
"start_date": "2025-02-01",
"payment_schedule": "4_cheques"
}
Response 201:
{
"data": {
"id": "deal_abc123",
"status": "draft",
...
}
}
الحصول على تفاصيل الصفقة:
GET /v1/deals/{deal_id}
Response 200: كائن الصفقة الكامل مع الأطراف والمستندات والمدفوعات المتداخلة
تحديث صفقة:
PATCH /v1/deals/{deal_id}
{
"annual_rent": 90000,
"status": "active"
}
Parties API
إنشاء مستأجر:
POST /v1/parties/tenants
{
"name": "فاطمة علي",
"emirates_id": "784-1990-2345678-2",
"mobile": "+971509876543",
"email": "fatima@email.com"
}
البحث عن الأطراف:
GET /v1/parties?search=أحمد&type=tenant&limit=20
Documents API
رفع مستند:
POST /v1/deals/{deal_id}/documents
Content-Type: multipart/form-data
file: @emirates-id-front.pdf
category: tenant_emirates_id
Response 201:
{
"data": {
"id": "doc_xyz789",
"category": "tenant_emirates_id",
"filename": "emirates-id-front.pdf",
"extracted_data": {
"name": "عمر محمد خالد",
"id_number": "784-1988-1234567-1",
"expiry_date": "2028-12-31"
},
"verification_status": "verified"
}
}
Payments API
قائمة جدول المدفوعات:
GET /v1/deals/{deal_id}/payments
Response: مصفوفة من أقساط الدفع مع تواريخ الاستحقاق والمبالغ والحالة
تحديد الدفع كمستلم:
PATCH /v1/payments/{payment_id}
{
"status": "paid",
"paid_date": "2025-02-01",
"transaction_ref": "CHQ-12345"
}
حدود المعدل
وصول API متاح فقط لخطة Enterprise. حدود المعدل الافتراضية:
| الخطة | طلبات/دقيقة | طلبات/ساعة | حد الاندفاع |
|---|---|---|---|
| Enterprise | 300 | 15,000 | 600 |
عناوين حد المعدل (تُرجع في كل استجابة):
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1640000000
429 Too Many Requests:
{
"error": {
"code": "rate_limit_exceeded",
"message": "تم تجاوز حد المعدل. أعد المحاولة بعد 60 ثانية.",
"retry_after": 60
}
}
أفضل الممارسات: نفذ exponential backoff عند تلقي 429
Webhooks
نظرة عامة
إشعارات في الوقت الفعلي عند حدوث أحداث في TenancyDesk. لا يُطلب polling.
ضمان التسليم: تسليم مرة واحدة على الأقل مع إعادة محاولات تلقائية
التحقق من التوقيع: HMAC-SHA256 للأمان
إعداد Webhook
1. إنشاء نقطة نهاية webhook (خادمك):
# مثال Flask
from flask import Flask, request
import hmac
import hashlib
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
# التحقق من التوقيع
signature = request.headers.get('X-TenancyDesk-Signature')
payload = request.data
# حساب التوقيع المتوقع
secret = 'your_webhook_secret'
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
if signature != expected:
return 'توقيع غير صالح', 401
# معالجة الحدث
event = request.json
if event['type'] == 'payment.overdue':
send_reminder(event['data'])
return 'OK', 200
2. تسجيل webhook في TenancyDesk:
POST /v1/webhooks
{
"url": "https://your-domain.com/webhook",
"events": ["payment.overdue", "deal.status_changed"],
"description": "Webhook للإنتاج لتذكيرات الدفع"
}
Response 201:
{
"data": {
"id": "wh_abc123",
"url": "https://your-domain.com/webhook",
"secret": "whsec_xyz789...", // استخدم للتحقق من التوقيع
"events": ["payment.overdue", "deal.status_changed"],
"status": "active"
}
}
أحداث Webhook
يُصدر TenancyDesk 21 نوعًا من الأحداث عبر ست فئات.
أحداث الصفقة (5):
deal.created- تم إنشاء صفقة جديدةdeal.updated- تغيرت تفاصيل الصفقةdeal.status_changed- تغيّرت حالة الصفقة (مثل الانتقال إلى نشطة)deal.completed- تم إكمال الصفقةdeal.cancelled- تم إلغاء الصفقة
أحداث الدفع (4):
payment.created- تم إنشاء دفعة في جدول السدادpayment.received- تم تحديد الدفع كمدفوعpayment.overdue- فات تاريخ استحقاق الدفعpayment.bounced- ارتد الشيك
أحداث المستندات (4):
document.uploaded- تمت إضافة مستند جديدdocument.verified- تم التحقق من المستندdocument.rejected- فشل التحققdocument.expiring- المستند على وشك الانتهاء
أحداث الأطراف (3):
party.created- تمت إضافة طرف (مستأجر أو مالك)party.updated- تغيرت بيانات الطرفparty.kyc_completed- اكتمل التحقق من هوية الطرف (KYC)
أحداث المهام (3):
task.created- تم إنشاء مهمةtask.completed- تم إكمال المهمةtask.overdue- تجاوزت المهمة تاريخ استحقاقها
أحداث الامتثال (2):
compliance.issue_created- تم اكتشاف مشكلة امتثالcompliance.issue_resolved- تم حل مشكلة الامتثال
لا توجد أحداث webhook مخصصة لتسجيل إيجاري/توثيق — لا يتكامل TenancyDesk مباشرةً مع دائرة الأراضي والأملاك (DLD). عندما يسجّل فريقك رقم التسجيل أو يحدّث حالة تسجيل الصفقة، يُسلَّم هذا التغيير عبر حدث
deal.updatedالمذكور أعلاه.
هيكل Webhook Payload
الغلاف القياسي:
{
"id": "evt_abc123",
"type": "payment.overdue",
"created_at": "2025-12-26T10:30:00Z",
"data": {
"deal_id": "deal_xyz789",
"payment_id": "pay_123",
"tenant_name": "أحمد محمد",
"amount": 21250,
"due_date": "2025-01-01",
"cheque_number": "CHQ-001"
},
"company_id": "comp_abc"
}
منطق إعادة المحاولة
TenancyDesk يتبع معيار Hook0 الصناعي:
إعادات محاولة سريعة (أول 10 محاولات):
- المحاولة 1: فوري
- المحاولة 2: 5 ثوانٍ
- المحاولة 3: 25 ثانية
- المحاولة 4: 125 ثانية (2 دقيقة)
- المحاولات 5-10: exponential backoff حتى 5 دقائق
إعادات محاولة بطيئة (المحاولات 11-60):
- كل ساعة واحدة
- حتى 60 محاولة إجمالية (2.5 يوم)
الاستسلام بعد: 60 محاولة فاشلة
يجب أن تقوم نقطة النهاية الخاصة بك بـ:
- الاستجابة خلال 5 ثوانٍ
- إرجاع رمز حالة 200-299
- المعالجة بشكل غير متزامن (لا تحجب الاستجابة)
الأمان
التحقق من توقيع Webhook (HMAC-SHA256):
العنوان: X-TenancyDesk-Signature
حساب التوقيع المتوقع:
import hmac
import hashlib
def verify_webhook(payload, signature, secret):
expected = hmac.new(
secret.encode('utf-8'),
payload.encode('utf-8'),
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
# الاستخدام
if not verify_webhook(request.body, request.headers['X-TenancyDesk-Signature'], webhook_secret):
return 'غير مصرح', 401
أفضل ممارسات الأمان:
- تحقق دائماً من التوقيعات
- استخدم HTTPS فقط (webhooks مرفوضة إذا HTTP)
- نفذ idempotency (نفس الحدث قد يُسلم مرتين)
- سجل جميع محاولات webhook للتصحيح
أنماط التكامل الشائعة
النمط 1: مزامنة الصفقات مع CRM
حالة الاستخدام: كل صفقة TenancyDesk تُنشئ تلقائياً في Zoho CRM
التنفيذ:
- اشترك في webhook
deal.created - عند الاستلام، استدعِ Zoho CRM API:
# معالج webhook TenancyDesk if event['type'] == 'deal.created': deal = event['data'] zoho.create_lead({ 'name': deal['tenant_name'], 'value': deal['annual_rent'], 'source': 'TenancyDesk' })
النمط 2: أتمتة المحاسبة
حالة الاستخدام: الدفع المستلم → إنشاء فاتورة في QuickBooks
التنفيذ:
- اشترك في
payment.received - استدعِ QuickBooks API لإنشاء فاتورة
- حدّث الصفقة بمعرف فاتورة QB
النمط 3: لوحة تحكم مخصصة
حالة الاستخدام: لوحة تحليلات في الوقت الفعلي
التنفيذ:
- استطلاع نقطة
/dealsكل 5 دقائق - تجميع البيانات (صفقات حسب المرحلة، الإيرادات)
- عرض في لوحة تحكم مخصصة
معالجة الأخطاء
رموز حالة HTTP:
200 OK- نجاح201 Created- تم إنشاء المورد400 Bad Request- معلمات غير صالحة401 Unauthorized- مفتاح API غير صالح/مفقود403 Forbidden- أذونات غير كافية404 Not Found- المورد غير موجود422 Unprocessable Entity- فشل التحقق429 Too Many Requests- تم تجاوز حد المعدل500 Internal Server Error- مشكلة في الخادم (أعد المحاولة)
تنسيق استجابة الخطأ:
{
"error": {
"code": "validation_failed",
"message": "يجب أن يكون الإيجار السنوي أكبر من 0",
"field": "annual_rent",
"docs_url": "https://docs.tenancydesk.com/api/errors#validation"
}
}
معالجة الأخطاء:
response = requests.post(url, headers=headers, json=data)
if response.status_code == 422:
error = response.json()['error']
print(f"فشل التحقق في {error['field']}: {error['message']}")
elif response.status_code == 429:
retry_after = int(response.headers['Retry-After'])
time.sleep(retry_after)
# أعد الطلب
elif response.status_code >= 500:
# exponential backoff للإعادة
pass
أفضل الممارسات
تكاملات جاهزة للإنتاج:
- معالجة الأخطاء: التقط جميع أخطاء HTTP، نفذ منطق إعادة المحاولة
- Idempotency: استخدم عنوان
Idempotency-Keyلطلبات POST - حد المعدل: احترم حدود المعدل، نفذ backoff
- أمان Webhook: تحقق دائماً من توقيعات HMAC
- التسجيل: سجل جميع استدعاءات API للتصحيح
- المراقبة: تتبع زمن وصول API ومعدلات الأخطاء
- الإصدار: استخدم عنوان إصدار API إذا كنت تثبت إصداراً معيناً
مثال طلب جاهز للإنتاج:
import requests
import time
def create_deal_with_retry(deal_data, max_retries=3):
headers = {
'Authorization': f'Bearer {API_KEY}',
'Content-Type': 'application/json',
'Idempotency-Key': f'deal_{uuid4()}',
'X-API-Version': '2025-12-01'
}
for attempt in range(max_retries):
try:
response = requests.post(
'https://api.tenancydesk.com/v1/deals',
headers=headers,
json=deal_data,
timeout=10
)
if response.status_code == 201:
return response.json()['data']
elif response.status_code == 429:
wait = int(response.headers.get('Retry-After', 60))
time.sleep(wait)
continue
elif response.status_code >= 500:
time.sleep(2 ** attempt) # Exponential backoff
continue
else:
response.raise_for_status()
except requests.exceptions.Timeout:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt)
raise Exception("تم تجاوز الحد الأقصى لإعادة المحاولات")
قم ببناء تكاملات قوية تتوسع مع عملك. تم تصميم TenancyDesk API لعمليات تكامل بمستوى الإنتاج على بنية تحتية سحابية راسخة.
هل كانت هذه المقالة مفيدة؟
مقالات ذات صلة
ابقَ على اطلاع
احصل على أحدث الأدلة والنصائح والتحديثات على بريدك الإلكتروني.