API和Webhooks:将TenancyDesk与您的系统集成

已更新 2026年7月15日

企业级REST API和实时webhooks实现TenancyDesk与您现有业务系统之间的无缝集成。构建自定义工作流、自动化数据同步,并开发强大的集成方案。

API概述

架构

RESTful API遵循行业标准:

  • 基于资源的URL(/deals/parties/documents
  • HTTP方法:GET、POST、PUT、PATCH、DELETE
  • JSON请求/响应体
  • 标准HTTP状态码
  • 提供OpenAPI 3.0规范

Base URLhttps://api.tenancydesk.com/v1/

API文档https://docs.tenancydesk.com/api(交互式Swagger UI)


身份验证

API Keys(推荐)

生成API密钥

  1. 设置集成API Keys
  2. 点击生成新API密钥
  3. 名称:"Production Integration"(使用描述性名称)
  4. 权限范围:选择所需权限(read:deals、write:parties等)
  5. 立即复制密钥(仅显示一次)

认证头

curl -H "Authorization: Bearer sk_live_abc123xyz..." \
  https://api.tenancydesk.com/v1/deals

安全性

  • 安全存储(使用环境变量,不要写在代码中)
  • 每90天轮换一次
  • 为dev/staging/prod使用不同的密钥
  • 密钥泄露时立即撤销

JWT Bearer Tokens(OAuth 2.0 - 企业版)

适用于用户上下文操作

  1. 通过OAuth流程获取访问令牌
  2. 将令牌包含在Authorization头中
  3. 令牌有效期: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计划。 默认速率限制:

计划请求/分钟请求/小时突发限制
Enterprise30015,000600

速率限制响应头(每个响应中返回):

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中

实现方式

  1. 订阅deal.created webhook
  2. 收到通知后调用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中创建发票

实现方式

  1. 订阅payment.received事件
  2. 调用QuickBooks API创建发票
  3. 将QuickBooks发票ID回写到TenancyDesk交易记录中

模式3:自定义数据看板

使用场景:实时分析仪表板

实现方式

  1. 每5分钟轮询/deals端点
  2. 聚合数据(按阶段分类的交易数量、收入统计)
  3. 在自定义仪表板中展示

错误处理

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条建议

  1. 错误处理:捕获所有HTTP错误,实现重试逻辑
  2. 幂等性:对POST请求使用Idempotency-Key
  3. 速率限制:遵守速率限制,实现退避策略
  4. Webhook安全:始终验证HMAC签名
  5. 日志记录:记录所有API调用以便排查问题
  6. 监控:跟踪API延迟和错误率
  7. 版本管理:使用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专为基于成熟云基础设施的生产级集成而设计。

这篇文章有帮助吗?

相关文章

保持更新

订阅获取最新指南、提示和更新。

订阅即表示您同意接收来自TenancyDesk的电子邮件。可随时取消订阅。

还需要帮助?

我们的支持团队随时准备协助您解决任何问题。

联系我们