企业级REST API和实时webhooks实现TenancyDesk与您现有业务系统之间的无缝集成。构建自定义工作流、自动化数据同步,并开发强大的集成方案。
API概述
架构
RESTful API遵循行业标准:
- 基于资源的URL(
/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 - 企业版)
适用于用户上下文操作:
- 通过OAuth流程获取访问令牌
- 将令牌包含在Authorization头中
- 令牌有效期:1小时(刷新令牌: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": "Omar Khalid",
"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": "Omar Khalid",
"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": "Fatima Ali",
"emirates_id": "784-1990-2345678-2",
"mobile": "+971509876543",
"email": "fatima@email.com"
}
搜索当事方:
GET /v1/parties?search=Ahmed&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": "Omar Mohammed Khalid",
"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": "Rate limit exceeded. Please retry after 60 seconds.",
"retry_after": 60
}
}
最佳实践:收到429响应时实施指数退避(exponential backoff)策略
Webhooks
概述
当TenancyDesk中发生事件时发送实时通知。无需轮询。
送达保证:至少一次送达(at-least-once delivery),自动重试
签名验证: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 'Invalid signature', 401
# 处理事件
event = request.json
if event['type'] == 'payment.overdue':
send_reminder(event['data'])
return 'OK', 200
2. 在TenancyDesk中注册webhook:
POST /v1/webhooks
{
"url": "https://your-domain.com/webhook",
"events": ["payment.overdue", "deal.status_changed"],
"description": "Production webhook for payment reminders"
}
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— 合规问题已解决
Ejari/Tawtheeq 注册没有专门的 webhook 事件 — TenancyDesk 不与 DLD 直接集成。当您的团队录入注册号或更新交易的注册状态时,该变更通过上述
deal.updated事件传递。
Webhook负载结构
标准封装格式:
{
"id": "evt_abc123",
"type": "payment.overdue",
"created_at": "2025-12-26T10:30:00Z",
"data": {
"deal_id": "deal_xyz789",
"payment_id": "pay_123",
"tenant_name": "Omar Khalid",
"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次:指数退避,最长5分钟
慢速重试(第11-60次尝试):
- 每1小时一次
- 共计最多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 'Unauthorized', 401
安全最佳实践:
- 始终验证签名
- 仅使用HTTPS(HTTP请求将被拒绝)
- 实现幂等性处理(同一事件可能被送达两次)
- 记录所有webhook请求以便调试
常见集成模式
模式1:将交易同步至CRM
使用场景:每笔TenancyDesk交易自动创建到Zoho CRM中
实现方式:
- 订阅
deal.createdwebhook - 收到通知后调用Zoho CRM API:
# TenancyDesk webhook处理程序 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创建发票
- 将QuickBooks发票ID回写到TenancyDesk交易记录中
模式3:自定义数据看板
使用场景:实时分析仪表板
实现方式:
- 每5分钟轮询
/deals端点 - 聚合数据(按阶段分类的交易数量、收入统计)
- 在自定义仪表板中展示
错误处理
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": "Annual rent must be greater than 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"Validation error in {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:
# 使用指数退避重试
pass
最佳实践
构建生产级集成的7条建议:
- 错误处理:捕获所有HTTP错误,实现重试逻辑
- 幂等性:对POST请求使用
Idempotency-Key头 - 速率限制:遵守速率限制,实现退避策略
- 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("Max retries exceeded")
构建可靠的集成,与您的业务共同扩展。TenancyDesk API专为基于成熟云基础设施的生产级集成而设计。
这篇文章有帮助吗?
相关文章
保持更新
订阅获取最新指南、提示和更新。