---
doc_id: integration-platform-handbook
title: Aile 平臺整合外部對接手冊
description: Aile 平臺整合 v2.2 的應用註冊、租戶安裝、HMAC 簽名、OpenAPI 路徑重構、AIFF 寫入、訊息傳送、服務廣播、Webhook、Simulator 與第三方接入實戰手冊。
slug: /platform/integration-platform-handbook
product: Aile
category: platform-handbook
audience:
  - partner
  - developer
visibility: public
status: published
version: 2.2.0
owner: aile-platform
updated_at: 2026-06-26
tags:
  - Aile
  - Integration
  - OpenAPI
  - HMAC
  - 平台文件
rendered_html: /rendered/platform/integration-platform-handbook/
download: true
sidebar_position: 1
---

# Aile 平臺整合（Integration）外部對接手冊

**版本：** v2.2  
**最後更新：** 2026-06-26  
**適用對象：** 第三方系統整合開發者  
**服務模組：** `aile-service-integration`  
**程式碼分支：** `release`  

> v2.2 保留 AIFF 寫入規格、`pictureUrl` 主資料與 setting 級圖片、訊息傳送、服務廣播與 Simulator 聯調說明。
>
> v2.2+ 變更摘要（2026-06-25 release）：
> - **Breaking:** OpenAPI 路徑重構 — 從 `/integration/openapi/v1/<資源>` 改為 `/<資源>/v1/<動作>`。
> - **Breaking:** 所有 OpenAPI 端點統一為 POST，不再支援 GET。
> - **Breaking:** HMAC 簽名公式修正 — `integrationId + nonce + body`（不含 method/path）。
> - **Breaking:** Authorization 頭字首從 `HMAC-SHA256` 改為 `AILE`。
> - **新增:** AIFF 寫入介面（create/update/delete）。
> - **新增:** 訊息傳送介面（`POST /messages/v1/send`）。
> - **新增:** 群發廣播介面（`POST /messages/v1/broadcast/create`）。
> - **修正:** 事件資源域和事件型別列表（基於原始碼）。

---

## 目錄

1. [概述](#1-概述)
2. [核心概念與模型](#2-核心概念與模型)
3. [整合接入流程](#3-整合接入流程)
4. [OpenAPI 安全機制（HMAC 簽名）](#4-openapi-安全機制hmac-簽名)
5. [控制面 API（平臺管理端）](#5-控制面-api平臺管理端)
6. [OpenAPI（第三方呼叫 Aile）](#6-openapi第三方呼叫-aile)
   - 6.3.9 [傳送訊息](#639-傳送訊息)
   - 6.3.10 [建立並傳送服務廣播](#6310-建立並傳送服務廣播)
7. [Webhook 事件推送（Aile → 第三方）](#7-webhook-事件推送aile--第三方)
8. [事件資源域與事件型別參考](#8-事件資源域與事件型別參考)
9. [接入實戰指南（第三方開發步驟）](#9-接入實戰指南第三方開發步驟)
10. [參考實現：Simulator 模擬服務](#10-參考實現simulator-模擬服務)
11. [常見問題與錯誤碼](#11-常見問題與錯誤碼)

---

## 1. 概述

Aile 平臺整合（Platform Integration）是一套標準化的第三方系統接入框架，允許外部 SaaS 平臺以**租戶級別**安裝 Aile 服務，並交換業務事件與資料。

### 1.1 架構全景

```
┌─────────────────────────────────────────────────┐
│                 Aile 平臺                         │
│                                                    │
│  aile-service-integration                         │
│  ├── /integration/app/*        應用定義管理        │
│  ├── /integration/tenant/*     安裝生命週期管理    │
│  ├── /messages/*               訊息傳送 / 廣播     │
│  ├── /openapi/*                第三方 API（HMAC簽名）│
│  └── InternalEvent → Webhook → 第三方回撥          │
│                                                    │
│  aile-service-tenant (事件釋出)                    │
│  └── InternalEventTrigger → 業務事件發布           │
└─────────────────────────────────────────────────┘
         │ 控制面 (HTTP)          │ OpenAPI (HMAC)    │ Webhook (HMAC)
         ▼                        ▼                   ▼
┌─────────────────────────────────────────────────┐
│              第三方整合平臺（你）                   │
│  ├── 安裝介面 (接收 Aile 安裝請求)                 │
│  ├── OpenAPI 呼叫 (查詢/傳送訊息/群發廣播)         │
│  └── Webhook 接收 (接收 Aile 業務事件)             │
└─────────────────────────────────────────────────┘
```

### 1.2 與舊版 TenantAppModel 的關係

**注意：** `aile-service-job` 模組中有一個舊的 `TenantAppModel` / `TenantAppController` 體系，以 `/tenantapp`、`/webhook` 為路徑字首。**本手冊描述的是新的 Integration Platform 體系**（基於 `IntegrationAppModel` / `TenantIntegrationModel`），路徑字首為 `/integration/`。新體系相較舊體繫有以下增強：

| 特性 | 舊 TenantAppModel | 新 Integration Platform |
|------|-------------------|------------------------|
| 應用定義 | 無全域性應用定義 | IntegrationApp（獨立於租戶） |
| 安裝生命週期 | 簡單 CRUD | 完整狀態機 Pending→Active→Suspended→Deleted |
| 金鑰管理 | 固定 secretKey | HMAC appSecret + rotate-secret |
| 事件訂閱 | webHookTypes | subscribedEvents（事件資源域粒度） |
| 第三方回撥 | 無 | install/update/rotateSecret/uninstall |
| OpenAPI 目錄 | 無 | catalog 端點自動發現 |
| 安全隔離 | 簽名+租戶繫結 | integrationId + body 一致性校驗 + nonce |

---

## 2. 核心概念與模型

### 2.1 IntegrationApp（平臺應用定義）

這是由 Aile 管理員建立的**全域性應用註冊**，描述一個第三方平臺的身份和能力。

```json
{
  "appId": "your-app-id",           // 全域性唯一應用 ID
  "appName": "Your Platform Name",  // 應用名稱
  "provider": "your-company",       // 提供方
  "supportedEvents": [              // 支援的事件資源域（*. 為通配）
    "contact.*",
    "service_number.*"
  ],
  "authType": "HMAC_SHA256",        // 鑑權型別
  "secret": "platform-secret",      // 平臺級金鑰（Aile 呼叫第三方時使用）
  "installUrl": "https://your-platform.com/api/aile/install",     // 第三方安裝 URL
  "updateUrl": "https://your-platform.com/api/aile/update",       // 第三方更新 URL
  "rotateSecretUrl": "https://your-platform.com/api/aile/rotate", // 第三方輪換金鑰 URL
  "uninstallUrl": "https://your-platform.com/api/aile/uninstall", // 第三方解除安裝 URL
  "installAckMode": "Sync",         // Sync（同步完成）或 Async（非同步受理）
  "status": "Active"                // Draft → Active → Suspended → Deleted
}
```

> ──→ 狀態機：`Draft → Active → Suspended → Active → Deleted`

### 2.2 TenantIntegration（租戶安裝例項）

當一個 Aile 租戶安裝你的平臺應用時，會在 Aile 端建立一個安裝例項：

```json
{
  "integrationId": "ti_xxxxx",       // 安裝例項唯一 ID（Aile 生成）
  "appId": "your-app-id",            // 關聯的應用 ID
  "tenantId": "T001",                // Aile 租戶 ID
  "tenantType": "enterprise",        // 租戶型別
  "externalTenantId": "EXT-12345",   // 第三方系統對應租戶 ID（由你指定）
  "appSecret": "generated-secret",   // 安裝級金鑰（用於 HMAC 簽名，由 Aile 生成）
  "webhookUrl": "https://your-platform.com/webhook/events", // 事件接收地址
  "subscribedEvents": [              // 實際訂閱的事件資源域
    "contact.*",
    "service_number.*"
  ],
  "installAckMode": "Sync",          // 受理模式
  "status": "Active"                 // 安裝狀態
}
```

> ──→ 安裝狀態機：`Pending → Active → Suspended/Disabled → Active → Deleted`  
> `InstallFailed` 狀態表示安裝失敗。

### 2.3 租戶隔離模型

- 一個 `IntegrationApp` 可以被多個租戶安裝（多個 `TenantIntegration`）
- 每個 `TenantIntegration` 擁有獨立的 `appSecret` 和 `webhookUrl`
- OpenAPI 呼叫時，第三方只能訪問該安裝例項所屬租戶的資料
- 跨系統租戶對映透過 `externalTenantId` 欄位管理（**不存在獨立的 TenantMapping 模型**）

---

## 3. 整合接入流程

### 3.1 整體流程圖

```
Step 0: Aile 管理員註冊 IntegrationApp（後臺操作）
  │
Step 1: Aile 租戶發起安裝
  POST /integration/tenant/system/v1/install
  { appId, tenantId, tenantType }
  │
  ├── Aile 建立 TenantIntegration（狀態=Pending）
  ├── Aile 呼叫第三方 installUrl（傳遞 integrationId、appSecret 等）
  │
Step 2: 第三方處理安裝
  ├── 驗證請求合法性
  ├── 建立外部租戶關聯
  ├── 確認 webhookUrl 和 subscribedEvents
  │
  ├── Sync 模式：直接返回 { status: "Active", externalTenantId, webhookUrl, subscribedEvents }
  │      → Aile 更新狀態為 Active
  │
  └── Async 模式：先返回 { accepted: true, status: "Pending" }
         → 後續回撥 Aile
         POST /integration/tenant/open/v1/install/callback
         { integrationId, status: "Active", externalTenantId, webhookUrl, subscribedEvents }
         → Aile 更新狀態為 Active

Step 3: 日常執行
  ├── 第三方呼叫 Aile OpenAPI（HMAC 簽名，查詢/同步資料）
  └── Aile 推送業務事件到第三方 webhookUrl（HMAC 簽名）

Step 4: 解除安裝
  POST /integration/tenant/system/v1/uninstall?integrationId=xxx
  ├── Aile 呼叫第三方 uninstallUrl
  └── TenantIntegration 狀態 = Deleted
```

### 3.2 Aile 傳送給第三方的安裝請求

當租戶發起安裝後，Aile 會呼叫 `IntegrationApp.installUrl`，傳送的請求體：

```json
{
  "integrationId": "ti_xxxxx",
  "appId": "your-app-id",
  "tenantId": "T001",
  "tenantType": "enterprise",
  "operatorId": "emp_001",
  "appSecret": "generated-secret-for-this-instance",
  "installationCallbackUrl": "https://aile-api.com/integration/tenant/open/v1/install/callback",
  "installAckMode": "Sync",
  "subscribedEvents": ["contact.*", "service_number.*"]
}
```

### 3.3 第三方應返回的安裝響應

**Sync 模式（`installAckMode=Sync`）：**

```json
{
  "status": "Active",
  "externalTenantId": "EXT-12345",
  "webhookUrl": "https://your-platform.com/webhook/events",
  "subscribedEvents": ["contact.*", "service_number.*"]
}
```

**Async 模式（`installAckMode=Async`）：**

```json
{
  "accepted": true,
  "status": "Pending"
}
```

之後非同步回撥 Aile：

```bash
curl -X POST https://aile-api.com/integration/tenant/open/v1/install/callback \
  -H "Content-Type: application/json" \
  -d '{
    "integrationId": "ti_xxxxx",
    "status": "Active",
    "externalTenantId": "EXT-12345",
    "webhookUrl": "https://your-platform.com/webhook/events",
    "subscribedEvents": ["contact.*", "service_number.*"]
  }'
```

### 3.4 第三方更新 / 輪換金鑰 / 解除安裝介面

Aile 在以下場景會呼叫第三方對應 URL（定義在 IntegrationApp 中）：

| 操作 | Aile 呼叫 | 傳遞引數 |
|------|----------|---------|
| 更新配置 | `updateUrl` | `{ integrationId, webhookUrl, subscribedEvents }` |
| 輪換金鑰 | `rotateSecretUrl` | `{ integrationId, operatorId }` |
| 解除安裝 | `uninstallUrl` | `{ integrationId }` |

---

## 4. OpenAPI 安全機制（HMAC 簽名）

所有第三方呼叫 Aile OpenAPI 的請求都必須攜帶 HMAC 簽名。Aile 推送的 Webhook 事件也會使用相同簽名機制。

### 4.1 簽名演算法

```
演演算法：  HMAC-SHA256
金鑰：    appSecret（來自 TenantIntegration 安裝例項）
簽名內容： integrationId + nonce + requestBody
簽名結果： Base64( HMAC-SHA256(appSecret, 簽名內容) )
```

### 4.2 請求頭規範

| Header | 值 | 說明 |
|--------|-----|------|
| `Authorization` | `AILE {integrationId}:{signature}` | 簽名身份與結果 |
| `X-Aile-Nonce` | `nonce_1718256000123` | 每次請求唯一的隨機字串 |
| `Content-Type` | `application/json` | 請求體格式 |

### 4.3 簽名示例（Python）

```python
import hmac
import hashlib
import base64
import time
import requests

def build_signature(integration_id: str, app_secret: str, nonce: str, body: str) -> str:
    """
    計算 HMAC-SHA256 簽名。
    簽名內容 = integrationId + nonce + body
    """
    raw = integration_id + nonce + body
    mac = hmac.new(
        app_secret.encode('utf-8'),
        raw.encode('utf-8'),
        hashlib.sha256
    )
    return base64.b64encode(mac.digest()).decode('utf-8')

def build_auth_header(integration_id: str, signature: str) -> str:
    return f"AILE {integration_id}:{signature}"

def call_aile_openapi(integration_id: str, app_secret: str, 
                       method: str, path: str, body: dict = None) -> dict:
    """
    呼叫 Aile OpenAPI。
    path 格式: /tenants/v1/me
    """
    base_url = "https://aile-api.com"  # 替換為實際環境地址
    body_str = json.dumps(body) if body else ""
    nonce = f"nonce_{int(time.time() * 1000)}"
    
    signature = build_signature(integration_id, app_secret, nonce, body_str)
    authorization = build_auth_header(integration_id, signature)
    
    response = requests.request(
        method=method,
        url=f"{base_url}{path}",
        headers={
            "Authorization": authorization,
            "X-Aile-Nonce": nonce,
            "Content-Type": "application/json"
        },
        data=body_str if body_str else None
    )
    return response.json()
```

### 4.4 簽名示例（JavaScript / Node.js）

```javascript
import crypto from "crypto";

function buildSignature(integrationId, appSecret, nonce, bodyString) {
  const raw = integrationId + nonce + (bodyString ?? "");
  return crypto.createHmac("sha256", appSecret).update(raw).digest("base64");
}

function buildAuthHeader(integrationId, signature) {
  return `AILE ${integrationId}:${signature}`;
}

async function callAileOpenApi(integrationId, appSecret, method, path, body) {
  const bodyStr = body ? JSON.stringify(body) : "";
  const nonce = `nonce_${Date.now()}`;
  const signature = buildSignature(integrationId, appSecret, nonce, bodyStr);
  
  const response = await fetch(`https://aile-api.com${path}`, {
    method,
    headers: {
      "Authorization": buildAuthHeader(integrationId, signature),
      "X-Aile-Nonce": nonce,
      "Content-Type": "application/json"
    },
    body: bodyStr || undefined
  });
  return response.json();
}
```

### 4.5 簽名示例（Java）

```java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class AileHmacSigner {
    public static String buildSignature(String integrationId, String appSecret, 
                                         String nonce, String body) {
        String raw = integrationId + nonce + (body != null ? body : "");
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(appSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
            return Base64.getEncoder().encodeToString(mac.doFinal(raw.getBytes(StandardCharsets.UTF_8)));
        } catch (Exception e) {
            throw new RuntimeException("HMAC signing failed", e);
        }
    }
    
    public static String buildAuthHeader(String integrationId, String signature) {
        return "AILE " + integrationId + ":" + signature;
    }
}
```

### 4.6 簽名驗證流程（服務端）

Aile 服務端驗證流程：

1. 過濾器攔截所有 OpenAPI 資源路徑（例如 `/tenants/v1/me`、`/contacts/v1/list`、`/aiffs/v1/create`）
2. 從 `Authorization` 頭解析 `integrationId` 和 `requestSignature`
3. 從請求體中解析 `integrationId` 欄位，與簽名中的身份一致性校驗（防止冒用）
4. 根據 `integrationId` 查詢 `TenantIntegrationModel`，取得 `appSecret`
5. 使用 `appSecret` 對 `integrationId + nonce + body` 計算 HMAC-SHA256
6. 使用 `MessageDigest.isEqual()` 時間恆定比對簽名
7. 校驗安裝例項狀態為 `Active`、所屬應用狀態為 `Active`

---

## 5. 控制面 API（平臺管理端）

以下 API 供 Aile 內部管理面使用，**第三方不需要呼叫這些介面**。列於此處供理解完整流程。

### 5.1 IntegrationApp（應用定義）

| 方法 | 路徑 | 說明 |
|------|------|------|
| `POST` | `/integration/app/system/v1/create` | 建立應用定義 |
| `POST` | `/integration/app/system/v1/update` | 更新應用定義 |
| `GET` | `/integration/app/system/v1/detail?appId=` | 查詢應用詳情 |
| `POST` | `/integration/app/system/v1/items` | 分頁查詢應用列表 |
| `POST` | `/integration/app/system/v1/enable` | 上架應用 |
| `POST` | `/integration/app/system/v1/disable` | 下架應用 |
| `POST` | `/integration/app/system/v1/delete` | 刪除應用 |
| `GET` | `/integration/app/system/v1/statistics/usage?appId=` | 使用統計 |

### 5.2 TenantIntegration（安裝管理）

| 方法 | 路徑 | 說明 |
|------|------|------|
| `POST` | `/integration/tenant/system/v1/install` | 發起安裝 |
| `POST` | `/integration/tenant/system/v1/update` | 更新安裝配置 |
| `POST` | `/integration/tenant/system/v1/suspend?integrationId=` | 暫停安裝 |
| `POST` | `/integration/tenant/system/v1/resume?integrationId=` | 恢復安裝 |
| `POST` | `/integration/tenant/system/v1/uninstall?integrationId=` | 解除安裝 |
| `POST` | `/integration/tenant/system/v1/rotate-secret?integrationId=` | 輪換金鑰 |
| `GET` | `/integration/tenant/system/v1/detail?integrationId=` | 查詢安裝詳情 |
| `GET` | `/integration/tenant/system/v1/items?tenantId=` | 依租戶查詢列表 |
| `GET` | `/integration/tenant/system/v1/items/by-app?appId=` | 依應用查詢列表 |
| `POST` | `/integration/tenant/open/v1/install/callback` | 第三方安裝回撥（公開介面） |

### 5.3 安裝請求引數詳情

**發起安裝：**

```json
{
  "appId": "your-app-id",       // 必填
  "tenantId": "T001",           // 必填
  "tenantType": "enterprise"    // 必填
}
```

**安裝回撥（第三方 → Aile）：**

```json
{
  "integrationId": "ti_xxxxx",        // 必填
  "status": "Active",                 // 必填（Active 或 InstallFailed）
  "externalTenantId": "EXT-12345",    // 可選但不建議為空
  "webhookUrl": "https://...",        // 可選
  "subscribedEvents": ["contact.*"],   // 可選
  "message": "安裝完成"                // 可選
}
```

**更新配置：**

```json
{
  "integrationId": "ti_xxxxx",
  "webhookUrl": "https://new-url.com/webhook",
  "subscribedEvents": ["tenant.*", "contact.*"]
}
```

---

## 6. OpenAPI（第三方呼叫 Aile）

所有 OpenAPI 端點採用 `/<資源>/v1/<動作>` 路徑結構（例如 `/contacts/v1/list`），**所有請求均為 POST 方法（含查詢請求）**，且必須攜帶 HMAC 簽名。舊版 `/integration/openapi/v1/<資源>` 路徑不再作為對外穩定契約。

### 6.1 通用請求格式

所有 OpenAPI 請求體都必須包含 `integrationId`：

```json
{
  "integrationId": "ti_xxxxx"
}
```

其餘欄位因 API 而異。詳見各端點說明。

### 6.2 通用響應格式

```json
{
  "code": 200,
  "message": "success",
  "data": { ... }
}
```

### 6.3 API 端點一覽

#### 6.3.1 租戶資訊

| 方法 | 路徑 | 說明 |
|------|------|------|
| `POST` | `/tenants/v1/me` | 查詢當前安裝例項的租戶資訊 |

**請求：**
```json
{
  "integrationId": "ti_xxxxx"
}
```

**響應 `data`：**
```json
{
  "tenantId": "T001",
  "tenantName": "某某公司",
  "tenantType": "enterprise",
  "status": "Active"
}
```

#### 6.3.2 服務號

| 方法 | 路徑 | 說明 |
|------|------|------|
| `POST` | `/service-numbers/v1/list` | 查詢服務號列表 |
| `POST` | `/service-numbers/v1/detail` | 查詢服務號詳情 |
| `POST` | `/service-numbers/v1/sync` | 查詢服務號差異同步列表 |

**列表請求：**
```json
{
  "integrationId": "ti_xxxxx",
  "current": 1,
  "size": 20
}
```

**詳情請求：**
```json
{
  "integrationId": "ti_xxxxx",
  "serviceNumberId": "SN001"
}
```

**同步請求：**
```json
{
  "integrationId": "ti_xxxxx"
}
```

#### 6.3.3 員工

| 方法 | 路徑 | 說明 |
|------|------|------|
| `POST` | `/employees/v1/list` | 查詢租戶員工列表 |

```json
{
  "integrationId": "ti_xxxxx",
  "current": 1,
  "size": 20
}
```

#### 6.3.4 聯絡人（客戶）

| 方法 | 路徑 | 說明 |
|------|------|------|
| `POST` | `/contacts/v1/list` | 查詢聯絡人列表 |
| `POST` | `/contacts/v1/detail` | 查詢聯絡人詳情 |
| `POST` | `/contacts/v1/interactions` | 查詢近期互動記錄 |

**列表請求：**
```json
{
  "integrationId": "ti_xxxxx",
  "serviceNumberId": "SN001",
  "current": 1,
  "size": 20
}
```

**詳情請求：**
```json
{
  "integrationId": "ti_xxxxx",
  "contactId": "C001",
  "serviceNumberId": "SN001"
}
```

**互動記錄請求：**
```json
{
  "integrationId": "ti_xxxxx",
  "serviceNumberId": "SN001",
  "days": 30,
  "current": 1,
  "size": 20
}
```

#### 6.3.5 群組（LineGroup）

| 方法 | 路徑 | 說明 |
|------|------|------|
| `POST` | `/groups/v1/list` | 查詢群組列表 |

```json
{
  "integrationId": "ti_xxxxx",
  "current": 1,
  "size": 20
}
```

#### 6.3.6 會話統計

| 方法 | 路徑 | 說明 |
|------|------|------|
| `POST` | `/sessions/v1/statistics` | 查詢會話月統計 |
| `POST` | `/sessions/v1/statistics/detail` | 查詢會話月統計詳情 |

```json
{
  "integrationId": "ti_xxxxx",
  "serviceNumberId": "SN001",
  "month": "2026-06"
}
```

#### 6.3.7 AIFF 配置（應用內浮動框架）

> **v2.2 更新。** 新增 create / update / delete 寫介面，支援主資料圖片 (`pictureUrl`) 和 setting 級別配置。

| 方法 | 路徑 | 說明 |
|------|------|------|
| `POST` | `/aiffs/v1/list` | 查詢 AIFF 列表（分頁） |
| `POST` | `/aiffs/v1/detail` | 查詢 AIFF 詳情（含 settings） |
| `POST` | `/aiffs/v1/create` | 建立 AIFF 主資料與設定 |
| `POST` | `/aiffs/v1/update` | 更新 AIFF 主資料與設定 |
| `POST` | `/aiffs/v1/delete` | 刪除 AIFF 主資料與全部設定 |

**列表請求：**
```json
{
  "integrationId": "ti_xxxxx"
}
```

**詳情請求：**
```json
{
  "integrationId": "ti_xxxxx",
  "extraId": "your-app-id"
}
```

**建立請求 `OpenApiAiffCreateRequestDto`：**

| 欄位 | 型別 | 必填 | 說明 |
|------|------|------|------|
| `integrationId` | String | ✅ | 租戶安裝例項 ID |
| `extraId` | String | ✅ | 外部應用唯一識別（外部系統中的 AIFF ID） |
| `name` | String | ✅ | AIFF 應用名稱 |
| `description` | String | | AIFF 應用描述 |
| `pictureUrl` | String | | **AIFF 主資料圖片地址**，直接儲存外部傳入的 URL（不下載、不上傳） |
| `settings` | Object[] | | 建立時同步寫入的 AIFF 設定列表（可選） |

**更新請求 `OpenApiAiffUpdateRequestDto`：**

| 欄位 | 型別 | 必填 | 說明 |
|------|------|------|------|
| `integrationId` | String | ✅ | 租戶安裝例項 ID |
| `extraId` | String | ✅ | 外部應用唯一識別 |
| `name` | String | ✅ | AIFF 應用名稱 |
| `description` | String | | AIFF 應用描述 |
| `pictureUrl` | String | | **AIFF 主資料圖片地址** |
| `settings` | Object[] | | 更新時同步維護的 AIFF 設定列表（可選） |

**刪除請求：**
```json
{
  "integrationId": "ti_xxxxx",
  "extraId": "your-app-id"
}
```
> 刪除主資料時會連帶刪除該 AIFF 下的所有設定。

**Setting 子結構 `OpenApiAiffSettingRequestDto`：**

> 每個 setting 對應一組前端展示配置，可在建立/更新 AIFF 時一併提交。

| 欄位 | 型別 | 必填 | 說明 |
|------|------|------|------|
| `id` | String | | 既有設定 ID（更新既有設定時使用） |
| `extraKey` | String | | 外部設定唯一識別 |
| `displayName` | String | | 前端展示名稱 |
| `pictureUrl` | String | | **設定級別圖片地址**。未傳時查詢返回回退外層主資料 `pictureUrl` |
| `serviceNumberIds` | String[] | | 設定生效的服務號 ID 列表 |
| `displayType` | String | | 顯示型別，見下表 |
| `roomType` | String[] | | **聊天室型別列表**（僅 `displayType=Room` 時生效），見下表 |
| `displayLocation` | String | | 嵌入位置（僅 `displayType=Room` 時生效），見下表 |
| `endPointUrl` | String | | AIFF 跳轉目標地址 |
| `supportDevice` | String | | 支援裝置型別，見下表 |
| `popupSize` | String | | 彈窗尺寸，見下表 |

**`displayType` 列舉值：**

| 值 | 說明 |
|------|------|
| `Frame` | 主框架 |
| `AppList` | 應用列表 |
| `Room` | 聊天室內嵌（此時需設定 `roomType` + `displayLocation`） |
| `ContactIndex` | 客戶主頁 |

**`roomType` 列舉值（可多選）：**

| 值 | 說明 |
|------|------|
| `One` | 單人聊天室 |
| `Person` | 個人聊天室 |
| `Friend` | 好友聊天室 |
| `System` | 系統聊天室 |
| `Discuss` | 多人聊天室 |
| `Group` | 社團聊天室 |
| `GroupOwner` | 社團聊天室（擁有者） |
| `BusinessCustomer` | 商務號聊天室（客戶角度） |
| `BusinessEmployee` | 商務號聊天室（員工角度） |
| `BusinessSecretary` | 商務號秘書群聊天室 |
| `ServiceNumberCustomer` | 服務號聊天室（客戶角度） |
| `ServiceNumberEmployee` | 服務號聊天室（員工角度） |
| `ServiceNumberAgent` | 服務號聊天室（服務人員角度） |
| `ServiceNumberGroup` | 服務號群內聊天室 |
| `Object` | 物件聊天室 |

**`displayLocation` 列舉值：**

| 值 | 說明 |
|------|------|
| `Toolbar` | 工具列 |
| `Extend` | 擴充套件欄位 |
| `Embed` | 下沉式（內嵌） |
| `MessageMenu` | 訊息選單 |

**`supportDevice` 列舉值：**

| 值 | 說明 |
|------|------|
| `Desktop` | 桌面端 |
| `Mobile` | 手機端 |

**`popupSize` 列舉值：**

| 值 | 說明 |
|------|------|
| `Full` | 全螢幕 |
| `Half` | 半螢幕 |

**詳情響應 `data` 結構 `OpenApiAiffVO`：**

```json
{
  "id": "aiff-xxx",
  "extraId": "your-app-id",
  "tenantId": "T001",
  "name": "外部應用名稱",
  "description": "應用描述",
  "pictureUrl": "https://example.com/image.png",
  "settings": [
    {
      "id": "setting-001",
      "extraId": "your-app-id",
      "extraKey": "setting-key-1",
      "displayName": "設定顯示名稱",
      "pictureUrl": "https://example.com/setting-icon.png",
      "endPointUrl": "https://example.com/aiff",
      "serviceNumberIds": ["SN001", "SN002"],
      "aiffId": "aiff-xxx"
    }
  ]
}
```

**重要說明：**

1. **圖片處理**：`pictureUrl` 僅做字串透傳與儲存，不下載圖片、不上傳附件。setting 未傳 `pictureUrl` 時，寫入階段不自動補外層值，僅查詢返回時回退外層主資料圖片
2. **icon 檔案上傳**：OpenAPI 僅承接結構化資料，不承接 `icon` 檔案上傳（內部 job 服務的 `multipart/form-data` icon 欄位在 OpenAPI 鏈路中固定為空）
3. **setting 定位**：更新時依 `extraKey` 定位既有 setting；若未傳可識別的標識，更新行為依賴既有業務約束
4. **刪除級聯**：刪除 AIFF 主資料時會一併刪除該 `extraId` 下所有 setting

#### 6.3.8 API 目錄（自動發現）

| 方法 | 路徑 | 說明 |
|------|------|------|
| `POST` | `/catalog/v1/business-objects` | 查詢業務物件清單 |
| `POST` | `/catalog/v1/sync-resources` | 查詢支援同步的資源清單 |
| `POST` | `/catalog/v1/event-types` | 查詢支援的事件型別清單 |
| `POST` | `/catalog/v1/event-scopes` | 查詢支援的事件資源域清單 |

所有目錄端點請求格式相同：
```json
{
  "integrationId": "ti_xxxxx"
}
```

#### 6.3.9 傳送訊息

> **v2.1 新增功能。** 供外部整合應用向指定聊天室傳送訊息。

| 方法 | 路徑 | 說明 |
|------|------|------|
| `POST` | `/messages/v1/send` | 傳送訊息到指定聊天室 |

**功能範圍：**

- 當前穩定支援的訊息型別：`Text`（文字訊息）、`Template`（模板訊息 / 卡片訊息）
- 不支援 `Image`、`File`、`Video`、`Audio`、`Voice`、`Sticker` 等需附件契約的型別（會返回請求非法）
- 支援 5 種接收目標路由：`Room`、`System`、`User`、`ServiceNumber`、`ServiceMember`
- 支援 3 種傳送者身份：`System`（系統訊息）、`User`（以使用者身份傳送）、`ServiceNumber`（以服務號身份傳送）
- 可透過 `sourceType` 控制單條訊息的顯示方式（`System` / `User` / `Assistant`）

**請求 DTO 結構：**

**頂層請求 `OpenApiMessageSendRequestDto`：**

| 欄位 | 型別 | 必填 | 說明 |
|------|------|------|------|
| `integrationId` | String | ✅ | 租戶安裝例項 ID |
| `sender` | Object | ✅ | 傳送者資料，見下表 |
| `to` | Object | ✅ | 接收者資料，見下表 |
| `messages` | Object[] | ✅ | 訊息列表（至少 1 條），見下表 |

**傳送者 `OpenApiMessageSenderDto`：**

| 欄位 | 型別 | 必填 | 說明 |
|------|------|------|------|
| `type` | String | ✅ | 傳送者型別：`System` / `User` / `ServiceNumber` |
| `userType` | String | | 使用者型別：`Employee` / `Contact`（type=User 時使用，預設為 Employee） |
| `code` | String | | openId 或服務號代號（type 非 System 時必填） |
| `incoming` | String | | 進線標記（可選） |

**接收者 `OpenApiMessageReceiverDto`：**

| 欄位 | 型別 | 必填 | 說明 |
|------|------|------|------|
| `type` | String | ✅ | 接收目標型別：`Room` / `System` / `User` / `ServiceNumber` / `ServiceMember` |
| `userType` | String | | 使用者型別：`Employee` / `Contact` / `Visitor`（type=User 時使用） |
| `code` | String | | 接收者代號（openId 或 roomId） |
| `serviceNumberId` | String | | 服務號 ID（type=ServiceNumber/ServiceMember 時使用） |
| `channelType` | String | | 指定渠道：`Line` / `Facebook` / `Webchat` / `Aiwow` 等 |

**單條訊息 `OpenApiMessageItemDto`：**

| 欄位 | 型別 | 必填 | 說明 |
|------|------|------|------|
| `type` | String | ✅ | 訊息型別：`Text` / `Template` |
| `altText` | String | | 替代文字（渠道不支援時的降級顯示） |
| `sourceType` | String | | 訊息來源型別：`User` / `System` / `Assistant` / `Broadcast`。未傳時按 sender.type 推導：System→System，其餘→User |
| `content` | Object | ✅ | 訊息內容，見下表 |

**訊息內容 `OpenApiMessageContentDto`：**

| 欄位 | 型別 | 必填 | 說明 |
|------|------|------|------|
| `type` | String | | 模板型別（訊息為 Template 時使用）：`Buttons` / `Confirm` / `Process` / `Carousel` |
| `title` | String | | 標題 |
| `text` | String | ✅ | 文字內容（Text 訊息的主體；Template 訊息的內文） |
| `imageUrl` | String | | 圖片網址（Template 訊息使用） |
| `orientation` | String | | 內容方向：`Vertical`（垂直）/ `Horizontal`（水平） |
| `defaultAction` | Object | | 預設動作（點選整張卡片的行為），結構：`{ type, label, url }` |
| `actions` | Object[] | | 互動動作列表（按鈕等），每項結構同上 |

**動作型別（`defaultAction` / `actions[].type`）：**

| 值 | 說明 |
|------|------|
| `Action` | 前端本地行為 |
| `Postback` | 提交到後端的回傳行為 |
| `Url` | 前端開啟外部連結 |
| `Aiff` | 前端開啟 Aiff 容器 |

**響應格式：**

```json
{
  "code": 200,
  "message": "success",
  "data": [
    {
      "id": "message-xxx",
      "roomId": "room-001",
      "tenantId": "T001",
      "type": "Text",
      "content": { "text": "Hello" },
      "senderId": "U001",
      "createdAt": "2026-06-23T10:30:00Z"
    }
  ]
}
```

**訊息路由規則說明：**

傳送訊息時，系統根據 `to.type` 和 `sender.type` 的組合決定最終的目標聊天室：

| to.type | sender.type | 路由邏輯 |
|---------|-------------|---------|
| `Room` | 任意 | 直接使用 `to.code` 作為 roomId |
| `System` | 任意 | 透過 `to.code`（員工 openId）定位員工的**系統聊天室** |
| `User` | `System` | 以接收者 openId 定位其**系統聊天室**（系統通知場景） |
| `User` | `User`（同一個 openId） | 查詢該使用者的**個人聊天室** |
| `User` | `User`（不同 openId） | 查詢傳送者的**好友聊天室**（非好友則報錯） |
| `User` | `ServiceNumber` | 查詢服務號擁有者與目標使用者的好友聊天室 |
| `ServiceNumber` | 任意 | 查詢指定服務號與指定客戶的**服務號聊天室** |
| `ServiceMember` | 任意 | 查詢指定服務號的**服務成員聊天室** |

#### 6.3.10 建立並傳送服務廣播

> **v2.1 新增功能。** 供外部整合應用建立並傳送服務號廣播（群發），內部走 tenant 服務既有廣播建立流程。

| 方法 | 路徑 | 說明 |
|------|------|------|
| `POST` | `/messages/v1/broadcast/create` | 建立並傳送服務廣播 |

**請求 DTO 結構 `OpenApiBroadcastCreateRequestDto`：**

| 欄位 | 型別 | 必填 | 說明 |
|------|------|------|------|
| `integrationId` | String | ✅ | 租戶安裝例項 ID |
| `openId` | String | ✅ | 本次建立廣播的**員工 OpenID**（用以解析建立者身份） |
| `serviceNumberId` | String | ✅ | 目標服務號 ID |
| `name` | String | ✅ | 廣播任務標題 |
| `remark` | String | | 廣播備註 |
| `channel` | String | ✅ | 廣播頻道：`Line` / `Facebook` / `Webchat` / `Aiwow` 等 |
| `content` | Object[] | ✅ | 廣播內容列表，每個元素為 `BroadcastBody` |
| `broadcastTime` | Long | | 預約廣播時間（Unix 毫秒時間戳），不填則立即傳送 |
| `labelIds` | String[] | | 標籤匹配範圍（以標籤篩選目標客戶） |
| `customerIds` | String[] | | 指定廣播目標客戶 ID 列表 |
| `matchLogic` | String | | 標籤匹配邏輯：`OR`（滿足任一）/ `AND`（全部滿足） |

**廣播內容 `BroadcastBody`：**

| 欄位 | 型別 | 必填 | 說明 |
|------|------|------|------|
| `index` | Integer | | 順序編號 |
| `type` | String | ✅ | 訊息型別（廣播支援：`Text` / `Image` / `File` / `Template`） |
| `content` | String | ✅ | 訊息內容（與既有服務號廣播格式一致） |

**響應格式：**

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "id": "broadcast-xxx",
    "serviceNumberId": "SN001",
    "name": "節日推廣",
    "status": "Doing",
    "createdAt": "2026-06-23T10:30:00Z"
  }
}
```

**重要說明：**

1. **建立者身份校驗**：`openId` 必須對應一個存在於當前安裝例項租戶中的員工，否則請求失敗
2. **標籤與客戶篩選**：`labelIds` 和 `customerIds` 都可選；若均不填，廣播目標將為空
3. **立即 vs 預約**：不填 `broadcastTime` 則立即嘗試傳送；填寫未來時間則排程傳送
4. **廣播狀態**：建立成功後狀態為 `Doing`，由 tenant 既有流程觸發下游廣播傳送；傳送完成後狀態變更為 `Done`
5. **許可權控制**：`integration` 服務會臨時建立 `AileContext`（含 tenantId + accountId + employeeId），完成後恢復原上下文，保證執行緒池安全

---

## 7. Webhook 事件推送（Aile → 第三方）

### 7.1 推送機制

- **方向：** Aile → 第三方平臺
- **協議：** HTTP POST
- **目標地址：** `TenantIntegration.webhookUrl`
- **內容型別：** `application/json`
- **簽名：** HMAC-SHA256（使用 `appSecret`，簽名規則同 OpenAPI）
- **冪等性：** 透過 `eventId` 保證，重複推送同一 eventId 應返回成功

### 7.2 事件信封格式 (EventEnvelope)

```json
{
  "eventId": "evt_abc123",
  "eventType": "contact.created",
  "eventVersion": "v1",
  "occurredAt": "2026-06-16T10:30:00Z",
  "source": "aile-tenant",
  "integration": {
    "appId": "your-app-id",
    "integrationId": "ti_xxxxx"
  },
  "tenant": {
    "tenantId": "T001",
    "externalTenantId": "EXT-12345",
    "tenantType": "enterprise"
  },
  "scope": {
    "serviceNumberId": "SN001",
    "entrySourceId": "line_channel_123"
  },
  "data": {
    "contactId": "C001",
    "name": "張三",
    "channel": "Line",
    "scopeId": "Uxxx_line_id"
  },
  "metadata": {
    "traceId": "trace_001",
    "retryCount": 0
  }
}
```

### 7.3 事件分發規則

1. **狀態過濾：** 僅向 `status=Active` 的安裝例項推送
2. **事件過濾：** 僅推送安裝例項 `subscribedEvents` 中訂閱的事件資源域
3. **安裝隔離：** 一個安裝例項推送失敗不影響其他安裝例項
4. **重試機制：** 推送失敗時進行重試，透過 `metadata.retryCount` 記錄

---

## 8. 事件資源域與事件型別參考

### 8.1 事件資源域（subscription scope codes）

用於 `supportedEvents`、`subscribedEvents` 的訂閱值：

| 資源域程式碼 | 說明 |
|-----------|------|
| `tenant.*` | 租戶事件 |
| `user.*` | 使用者事件 |
| `service_number.*` | 服務號事件 |
| `contact.*` | 聯絡人（客戶）事件 |
| `visitor.*` | 訪客事件 |
| `group.*` | 群組事件 |
| `addressbook.*` | 通訊錄事件 |
| `notice.*` | 通知事件 |
| `session.*` | 會話事件 |

### 8.2 目前已實現的事件型別

以下事件型別在 `aile-service-tenant` 中已實作並自動釋出：

| 事件型別 | 資源域 | 說明 |
|---------|--------|------|
| `service_number.created` | service_number.* | 服務號建立 |
| `service_number.updated` | service_number.* | 服務號更新 |
| `service_number.deleted` | service_number.* | 服務號刪除 |
| `contact.created` | contact.* | 聯絡人建立 |
| `contact.updated` | contact.* | 聯絡人更新 |
| `contact.deleted` | contact.* | 聯絡人刪除 |
| `contact.service_number_unfollowed` | contact.* | 聯絡人取消追蹤服務號 |
| `visitor.merged` | visitor.* | 匿名訪客合併為實名 |
| `employee.disabled` | employee.disabled | 員工停用 |
| `tenant.disabled` | tenant.* | 租戶停用 |

> **注意：** 當前事件推送機制（InternalEvent → Pub/Sub → Webhook HTTP）中，Pub/Sub 釋出已實現，但 Webhook HTTP 投遞層目前正在規劃中。具體可用性請與 Aile 團隊確認。

---

## 9. 接入實戰指南（第三方開發步驟）

### 9.1 前置準備

1. **聯絡 Aile 管理員**，取得：
   - `appId`（你的應用識別碼）
   - Aile API Base URL（例如 `https://api.aile.com`）
2. **準備你的伺服器端點：**
   - 安裝介面：`POST /your-platform/api/aile/install`
   - 更新介面：`POST /your-platform/api/aile/update`
   - 輪換金鑰介面：`POST /your-platform/api/aile/rotate`
   - 解除安裝介面：`POST /your-platform/api/aile/uninstall`
   - Webhook 接收介面：`POST /your-platform/webhook/events`

### 9.2 第一步：實現安裝介面

Aile 呼叫 `installUrl` 時，你的介面需要：

1. 接收引數並儲存關鍵資訊（特別是 `appSecret` 和 `integrationId`）
2. 為這個安裝建立 `externalTenantId`（你的系統中對應的租戶 ID）
3. 確定你的 Webhook 接收地址
4. 決定訂閱哪些事件資源域

**示例（Node.js / Express）：**

```javascript
app.post('/api/aile/install', (req, res) => {
  const { integrationId, appId, tenantId, tenantType, appSecret, 
          installationCallbackUrl, subscribedEvents } = req.body;
  
  // 儲存安裝資訊到你的資料庫
  db.installations.save({
    integrationId,
    appId,
    tenantId,
    tenantType,
    appSecret,          // 重要：後續所有 OpenAPI 呼叫都需要
    installationCallbackUrl,
    subscribedEvents,
    externalTenantId: `ext_${tenantId}`,
    webhookUrl: 'https://your-platform.com/webhook/events',
    status: 'Active'
  });
  
  // 同步模式直接返回成功
  res.json({
    status: 'Active',
    externalTenantId: `ext_${tenantId}`,
    webhookUrl: 'https://your-platform.com/webhook/events',
    subscribedEvents: ['contact.*', 'service_number.*']
  });
});
```

### 9.3 第二步：實現 OpenAPI 呼叫

使用儲存的 `appSecret` 和 `integrationId`，透過 HMAC 簽名呼叫 Aile API。

```javascript
// 查詢租戶資訊
const result = await callAileOpenApi(
  integrationId, appSecret,
  'POST',
  '/tenants/v1/me',
  { integrationId }
);

// 查詢服務號列表
const services = await callAileOpenApi(
  integrationId, appSecret,
  'POST',
  '/service-numbers/v1/list',
  { integrationId, current: 1, size: 20 }
);

// 查詢聯絡人列表
const contacts = await callAileOpenApi(
  integrationId, appSecret,
  'POST',
  '/contacts/v1/list',
  { integrationId, serviceNumberId: 'SN001', current: 1, size: 20 }
);
```

### 9.4 第三步：實現 Webhook 接收

接收 Aile 推送的業務事件：

```javascript
app.post('/webhook/events', (req, res) => {
  const event = req.body;
  
  // 冪等檢查：避免重複處理同一事件
  if (alreadyProcessed(event.eventId)) {
    return res.json({ success: true, duplicated: true });
  }
  
  // 根據事件型別進行業務處理
  switch (event.eventType) {
    case 'contact.created':
      handleContactCreated(event.data);
      break;
    case 'contact.updated':
      handleContactUpdated(event.data);
      break;
    case 'service_number.created':
      handleServiceNumberCreated(event.data);
      break;
    // ... 更多事件型別
  }
  
  markAsProcessed(event.eventId);
  res.json({ success: true, duplicated: false });
});
```

### 9.5 第四步：實現更新 / 輪換 / 解除安裝

```javascript
app.post('/api/aile/update', (req, res) => {
  const { integrationId, webhookUrl, subscribedEvents } = req.body;
  db.installations.update(integrationId, { webhookUrl, subscribedEvents });
  res.json({ status: 'Active' });
});

app.post('/api/aile/rotate', (req, res) => {
  const { integrationId, operatorId } = req.body;
  // 更新 appSecret（Aile 端會生成新金鑰，這裡接收通知）
  res.json({ status: 'Active' });
});

app.post('/api/aile/uninstall', (req, res) => {
  const { integrationId } = req.body;
  db.installations.update(integrationId, { status: 'Deleted' });
  res.json({ status: 'Deleted' });
});
```

### 9.6 重要注意事項

1. **所有 OpenAPI 請求都是 POST：** 包括查詢請求也使用 POST 方法
2. **請求體必須包含 integrationId：** 且必須與簽名中的 integrationId 一致
3. **Nonce 必須唯一：** 建議使用 `nonce_{timestamp}` 格式
4. **empty body 處理：** 無請求體時，`bodyString` 為空字串 `""`，簽名使用 `integrationId + nonce + ""`
5. **響應格式：** Aile 統一返回 `{ code: 200, message: "success", data: {...} }`
6. **HTTPS 強制：** 所有 API 通訊必須使用 HTTPS

---

## 10. 參考實現：Simulator 模擬服務

`integration-third-party-simulator` 是一個完整的第三方模擬服務，可用於本地開發聯調。

### 10.1 啟動方式

```bash
cd integration-third-party-simulator
cp .env.example .env
npm install
npm start
```

預設埠：`3301`

### 10.2 關鍵配置（.env）

```properties
PORT=3301
INSTALL_MODE=sync                  # sync 或 async
ASYNC_CALLBACK_DELAY_MS=200        # 非同步回撥延遲
ASYNC_FINAL_STATUS=Active          # 非同步最終狀態
DEFAULT_WEBHOOK_BASE_URL=http://localhost:3301
AILE_OPENAPI_BASE_URL=http://localhost:8080   # Aile 服務地址
```

### 10.3 模擬服務介面

**控制面（第三方平臺端，接收 Aile 請求）：**

```bash
# 安裝
curl -X POST http://localhost:3301/control-plane/install \
  -H "Content-Type: application/json" \
  -d '{
    "integrationId": "ti_001",
    "appId": "aipower",
    "tenantId": "tenant_001",
    "tenantType": "PERSONAL",
    "appSecret": "secret_001",
    "installationCallbackUrl": "http://localhost:8080/integration/tenant/open/v1/install/callback",
    "subscribedEvents": ["contact.*"]
  }'

# 更新
curl -X POST http://localhost:3301/control-plane/update \
  -H "Content-Type: application/json" \
  -d '{"integrationId": "ti_001", "webhookUrl": "https://new.example.com/webhook"}'

# 解除安裝
curl -X POST http://localhost:3301/control-plane/uninstall \
  -H "Content-Type: application/json" \
  -d '{"integrationId": "ti_001"}'
```

**除錯介面：**

```bash
# 檢視所有安裝
curl http://localhost:3301/debug/installations

# 檢視所有 Webhook 事件
curl http://localhost:3301/debug/webhooks

# 以安裝例項身份觸發 OpenAPI 呼叫
curl -X POST http://localhost:3301/debug/installations/ti_001/openapi/invoke \
  -H "Content-Type: application/json" \
  -d '{"method": "POST", "path": "/tenants/v1/me"}'
```

### 10.4 聯調建議

1. 將 `IntegrationApp.installUrl` 指向 `http://localhost:3301/control-plane/install`
2. 先用 `INSTALL_MODE=sync` 打通主鏈路
3. 再切 `async` 驗證回撥分支
4. 確保 `AILE_OPENAPI_BASE_URL` 指向可訪問的 Aile 環境

---

## 11. 常見問題與錯誤碼

### 11.1 常見錯誤碼

| 錯誤碼 | 說明 | 解決建議 |
|--------|------|---------|
| `FAIL_OPENAPI_AUTH_HEADER_REQUIRED` | 缺少 Authorization 或 X-Aile-Nonce 頭 | 檢查請求頭是否正確傳遞 |
| `FAIL_OPENAPI_SIGNATURE_INVALID` | 簽名驗證失敗 | 檢查簽名演算法（integrationId+nonce+body），確認 appSecret 正確 |
| `FAIL_OPENAPI_INTEGRATION_NOT_FOUND` | 安裝例項不存在或未啟用 | 檢查 integrationId 是否正確，安裝狀態是否為 Active |
| `FAIL_OPENAPI_INTEGRATION_DISABLED` | 安裝例項已停用 | 聯絡 Aile 管理員確認安裝狀態 |
| `FAIL_TENANT_INTEGRATION_NOT_FOUND` | 資料許可權校驗失敗 | 確認查詢的服務號或聯絡人是否屬於當前租戶 |
| `FAIL_INTEGRATION_APP_NOT_FOUND` | 應用定義不存在或已下架 | 聯絡 Aile 管理員確認應用狀態 |

### 11.2 FAQ

**Q: 簽名驗證一直失敗？**

A: 請確認簽名內容為 `integrationId + nonce + bodyString`（**注意不是 method + path + nonce + body**）。bodyString 為原始 JSON 字串，無 body 時為空字串。

**Q: Authorization 頭格式？**

A: 格式為 `AILE {integrationId}:{signature}`，例如 `AILE ti_001:abc123...`。

**Q: 查詢請求使用 GET 還是 POST？**

A: 所有 OpenAPI 端點均使用 **POST** 方法（含查詢請求），請求體為 JSON。

**Q: 如何判斷安裝是否為 Async 模式？**

A: 檢視請求中的 `installAckMode` 欄位。`Sync` 表示同步完成（直接返回狀態），`Async` 表示先受理再回撥。

**Q: 第三方需要實現哪些介面？**

A: 至少需要實現 `installUrl`（安裝），建議也實現 `updateUrl`、`uninstallUrl`；Webhook 接收介面用於接收事件。

---

## 附錄 A：關鍵檔案索引

| 層級 | 檔案路徑 | 說明 |
|------|---------|------|
| 應用定義模型 | `aile-api/aile-integration-api/.../model/IntegrationAppModel.java` | IntegrationApp 持久化模型 |
| 安裝例項模型 | `aile-api/aile-integration-api/.../model/TenantIntegrationModel.java` | TenantIntegration 持久化模型 |
| 事件信封 DTO | `aile-api/aile-integration-api/.../dto/event/EventEnvelopeDto.java` | 對外事件格式 |
| 安裝請求 DTO | `aile-api/aile-integration-api/.../dto/control/InstallationRequestDto.java` | 安裝請求引數 |
| 安裝響應 DTO | `aile-api/aile-integration-api/.../dto/control/InstallationResponse.java` | 第三方安裝響應 |
| 回撥請求 DTO | `aile-api/aile-integration-api/.../dto/control/InstallationCallbackRequestDto.java` | 非同步回撥引數 |
| 事件資源域列舉 | `aile-api/aile-integration-api/.../enums/IntegrationEventScopeType.java` | 9 個事件資源域定義 |
| 安裝狀態列舉 | `aile-api/aile-integration-api/.../enums/TenantIntegrationStatus.java` | 6 種安裝狀態 |
| HMAC 簽名校驗器 | `aile-service/aile-service-integration/.../service/impl/IntegrationOpenApiSignatureVerifier.java` | 入站簽名校驗實現 |
| OpenAPI 過濾器 | `aile-service/aile-service-integration/.../config/IntegrationOpenApiSignatureFilter.java` | 請求攔截過濾器 |
| 訪問驗證器 | `aile-service/aile-service-integration/.../service/impl/IntegrationOpenApiAccessValidator.java` | 安裝狀態與資料許可權校驗 |
| OpenAPI 服務 | `aile-service/aile-service-integration/.../service/impl/IntegrationOpenApiServiceImpl.java` | OpenAPI 業務邏輯 |
| 安裝控制器 | `aile-service/aile-service-integration/.../controller/TenantIntegrationController.java` | 安裝 API 端點 |
| 應用控制器 | `aile-service/aile-service-integration/.../controller/IntegrationAppController.java` | 應用管理 API 端點 |
| 事件型別常量 | `aile-service/aile-service-tenant/.../constants/TenantIntegrationEventTypes.java` | 已實現的業務事件型別 |
| Simulator 簽名 | `integration-third-party-simulator/src/utils/signature.js` | 簽名實現參考 |
| Simulator 安裝 | `integration-third-party-simulator/src/services/installationService.js` | 安裝流程參考 |
| 訊息傳送 DTO | `aile-api/aile-integration-api/.../dto/openapi/OpenApiMessageSendRequestDto.java` | 訊息傳送請求 |
| 訊息接收者 DTO | `aile-api/aile-integration-api/.../dto/openapi/OpenApiMessageReceiverDto.java` | 接收目標定義 |
| 訊息傳送者 DTO | `aile-api/aile-integration-api/.../dto/openapi/OpenApiMessageSenderDto.java` | 傳送者身份定義 |
| 訊息內容 DTO | `aile-api/aile-integration-api/.../dto/openapi/OpenApiMessageContentDto.java` | 訊息內容結構 |
| 訊息條目 DTO | `aile-api/aile-integration-api/.../dto/openapi/OpenApiMessageItemDto.java` | 單條訊息結構 |
| 廣播請求 DTO | `aile-api/aile-integration-api/.../dto/openapi/OpenApiBroadcastCreateRequestDto.java` | 廣播建立請求 |
| 廣播內容 DTO | `aile-api/aile-tenant-api/.../model/servicenumber/BroadcastBody.java` | 廣播訊息體 |
| 訊息控制器 | `aile-service/aile-service-integration/.../controller/IntegrationOpenApiMessageController.java` | 訊息與廣播 API 端點 |
| 訊息服務實現 | `aile-service/aile-service-integration/.../service/impl/IntegrationOpenApiMessageServiceImpl.java` | 訊息傳送與廣播業務邏輯 |
| AIFF 建立 DTO | `aile-api/aile-integration-api/.../dto/openapi/OpenApiAiffCreateRequestDto.java` | AIFF 建立請求 |
| AIFF 更新 DTO | `aile-api/aile-integration-api/.../dto/openapi/OpenApiAiffUpdateRequestDto.java` | AIFF 更新請求 |
| AIFF 刪除 DTO | `aile-api/aile-integration-api/.../dto/openapi/OpenApiAiffDeleteRequestDto.java` | AIFF 刪除請求 |
| AIFF Setting DTO | `aile-api/aile-integration-api/.../dto/openapi/OpenApiAiffSettingRequestDto.java` | AIFF 設定請求 |
| AIFF 控制器 | `aile-service/aile-service-integration/.../controller/IntegrationOpenApiAiffController.java` | AIFF CRUD API 端點 |
| RoomTypeEnum | `aile-api/aile-job-api/.../aiff/enums/RoomTypeEnum.java` | 15 種聊天室型別 |
| DisplayTypeEnum | `aile-api/aile-job-api/.../aiff/enums/DisplayTypeEnum.java` | 4 種顯示型別 |
| AiffModel | `aile-api/aile-job-api/.../aiff/model/AiffModel.java` | AIFF 主資料模型（含 pictureUrl） |

## 附錄 B：版本變更記錄

| 版本 | 日期 | 變更 |
|------|------|------|
| v2.2+ | 2026-06-25 | 在 v2.2 AIFF 規格基礎上補充 release 變更：OpenAPI 路徑由 `/integration/openapi/v1/<資源>` 重構為 `/<資源>/v1/<動作>`；所有端點統一 POST；HMAC 簽名公式確認為 `integrationId + nonce + body`；Authorization 字首改為 `AILE`；同步更新 AIFF 寫入、訊息傳送、群發廣播與事件資源域/事件型別說明。 |
| v2.2 | 2026-06-24 | AIFF 章節全面重寫：新增 create / update / delete 寫介面完整規格；補充 `pictureUrl` 圖片支援說明（主資料級 + setting 級）；補齊 5 組列舉表（displayType / roomType 15 種 / displayLocation / supportDevice / popupSize）；新增 setting 子結構 DTO 與詳情響應；更新附錄 A |
| v2.1 | 2026-06-23 | 新增 6.3.9 傳送訊息 API 完整規格 + 6.3.10 廣播 API 完整規格；更新架構全景圖；更新附錄 A 檔案索引 |
| v2.0 | 2026-06-16 | 基於 `release` 分支最新程式碼重寫；修正 HMAC 簽名演算法文件；補充 10 個 OpenAPI 控制器細節；增加目錄章節 |
| v1.0 | 2026-06-13 | 初版 |
