> For the complete documentation index, see [llms.txt](https://docs.maiagent.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.maiagent.ai/tech/api-integration/api-tool-engineering-guide.md).

# API 工具工程指南

把你的 REST API 接給 MaiAgent 的 Agent 當工具：請求怎麼組、認證怎麼帶、逾時與重試、錯誤怎麼回給模型、記錄怎麼查。

本頁寫給要「讓 Agent 反向呼叫自家系統」的工程師：MaiAgent 這一端怎麼把 Agent 決定的參數組成 HTTP 請求、你的 API 要長什麼樣子才好被模型使用、失敗時模型會看到什麼。

後台的逐步操作（在哪個畫面新增工具、每個欄位填什麼）請看使用者手冊的 [建立 API 工具](https://docs.maiagent.ai/build/tools/setup_api_tool)，本頁不重複操作步驟。工具也可以用 REST API 建立與管理，欄位與本頁一致，見 API 文件的「工具和連接器」。

## 一次呼叫的流程 <a href="#call-flow" id="call-flow"></a>

```mermaid
sequenceDiagram
    participant U as 使用者
    participant A as MaiAgent 的 Agent
    participant P as MaiAgent 平台
    participant S as 你的 API

    U->>A: 「幫我查訂單 A123 的狀態」
    A->>A: 依工具描述與參數結構決定呼叫哪個工具、填什麼參數
    A->>P: tool call（工具名稱＋JSON 參數）
    P->>P: 套用參數預設值、代入路徑參數與標頭、Query 參數的 {{變數}}
    P->>S: HTTP 請求（依參數位置放進路徑、query string、JSON body 或標頭）
    S-->>P: 回應（任何文字，建議 JSON）
    P->>P: 遮罩敏感值、依 context window 截斷、寫入工具執行記錄
    P-->>A: 回應原文（或錯誤訊息）
    A-->>U: 用自然語言整理結果
```

Agent 只負責「決定呼叫與參數」，實際的 HTTP 請求由平台送出；你的 API 不需要知道任何模型相關的事，只要是可從公網存取的 HTTP 端點即可。

## 工具定義對照 <a href="#tool-definition" id="tool-definition"></a>

一個 API 工具對應**一個端點＋一種 HTTP 方法**。平台目前不匯入 OpenAPI 文件；若你的 API 有 OpenAPI 規格，把每個要開放給 Agent 的 operation 各建成一個工具，把路徑參數、query 與 requestBody 的 schema 合併貼進「參數結構」即可。

每個欄位填什麼、格式限制與畫面位置，以使用者手冊的 [建立 API 工具：API 配置詳細設定](https://docs.maiagent.ai/build/tools/setup_api_tool#step-6-api-configure-details) 為準。從工程端整合時另外要知道：

* **工具名稱 `name` 是模型呼叫用的識別字。** 送給模型前會正規化成 `^[a-zA-Z][a-zA-Z0-9_]*$`、最長 64 字元，連字符會變底線，超長會截斷並補上雜湊尾碼。建議一開始就用 `snake_case`，例如 `get_order_status`。
* **模型判斷何時呼叫，只看工具描述。** `prompt` 有值就用 `prompt`，否則用 `description`；寫清楚做什麼、什麼情況用、回傳什麼。
* **用 REST API 建立工具時的欄位名稱**：API URL 為 `apiUrl`、HTTP 方法為 `httpMethod`（`get`、`post`、`put`、`patch`、`delete`）、標頭為 `rawHeaders`、Query 參數為 `rawQueryParams`、參數結構為 `rawParametersSchema`，顯示名稱為 `displayName`。

## 參數結構（JSON Schema） <a href="#parameters-schema" id="parameters-schema"></a>

平台會把參數結構轉成模型的 function-calling schema，模型回傳的參數再依它驗證。

```json
{
  "type": "object",
  "properties": {
    "order_id": {
      "type": "string",
      "description": "訂單編號，格式如 A123456"
    },
    "include_items": {
      "type": "boolean",
      "description": "是否一併回傳品項明細",
      "default": false
    },
    "status": {
      "type": "string",
      "enum": ["pending", "shipped", "delivered"],
      "description": "只查特定狀態時填"
    }
  },
  "required": ["order_id"]
}
```

規則與限制：

* 頂層必須是 `type: "object"`；支援 `properties`、`required`、`default`、`enum`、巢狀 `object` 與 `array`。
* 參數名稱只能用英文字母、數字、底線、點、連字符與中文，長度 1–64；不符合的名稱在儲存時就會被拒絕。
* **`default` 會由平台補值**：模型沒有提供該參數時，平台把 `default` 填進去再送出。要固定帶某個值、又不想讓模型決定，用 `default` 或直接寫在 Query 參數。
* 每個參數都寫 `description`。模型填參數的品質幾乎完全取決於這段文字，寫上格式範例（「格式如 A123456」）比只寫型別有效得多。
* 參數名稱 `timeout` 有保留意義：模型若提供它，平台會拿來當這次請求的逾時秒數，**不會**送給你的 API。不要用這個名字當業務參數。
* `required` 與型別交給模型端的 function calling 遵守；平台儲存工具時只檢查參數結構本身是合法的 JSON Schema，送出請求前只檢查路徑參數是否齊全。模型偶爾仍會漏掉必填參數，請讓你的 API 回 `400` 並說明缺哪個欄位，模型會重新填參數再呼叫（見[回應怎麼回給模型](#response-to-model)）。
* **修改參數結構後立即生效**：工具定義在每一則訊息開始處理時才讀取，儲存後的下一則訊息就用新的結構，進行中的對話也一樣，不需要重新掛載工具。MCP 工具的「允許的工具」清單則是儲存時取回的快照，伺服器端新增工具後要重新取得，見使用者手冊 [建立 MCP 工具](https://docs.maiagent.ai/build/tools/mcp-setup#step-5-fetch-allowed-tools)。

{% hint style="warning" %}
不要把 API 金鑰宣告成參數再靠 `default` 帶入。模型看得到參數結構，金鑰會進入模型上下文。金鑰請放在標頭或 Query 參數，見下一節。
{% endhint %}

## 請求怎麼組 <a href="#request-assembly" id="request-assembly"></a>

參數結構裡的每個參數可以指定位置（JSON Schema 屬性的 `location`：`path`、`query`、`body`、`header`），平台依位置把模型給的值放進請求。沒有指定位置時，依 HTTP 方法決定：

| 方法                   | 未指定位置的參數                                       | Body                |
| -------------------- | ---------------------------------------------- | ------------------- |
| `GET`                | 放進 query string                                | 只有指定為 `body` 的參數時才送 |
| `POST`、`PUT`、`PATCH` | 放進 JSON body（`Content-Type: application/json`） | 一律送出 JSON 物件        |
| `DELETE`             | **不送**；需要傳值時請指定位置                              | 只有指定為 `body` 的參數時才送 |

要點：

* **路徑參數**：API URL 寫成 `https://api.example.com/orders/{order_id}` 這種單層大括號 `{參數名}` 的形式，參數位置設為 `path`，值會經 URL 編碼後代入。這次呼叫缺任何一個路徑參數的值時，平台不送出請求，直接回報錯誤。規則與畫面操作見使用者手冊的 [路徑參數與參數位置](https://docs.maiagent.ai/build/tools/setup_api_tool#path-parameters)。
* **Query 參數同名時以工具設定為準**：工具設定的「Query 參數」與模型參數同名時，以設定值為準。把必須走 query string 的金鑰放在 Query 參數，模型就無法覆蓋它。
* **標頭同名時以工具設定為準**：位置為 `header` 的參數以參數名稱作為標頭送出；工具的「標頭」或聯絡人憑證已有同名標頭時（不分大小寫），以設定的值為準。
* Body 一律是 JSON 物件，不支援 form-data、檔案上傳或 XML。需要這些格式的 API 請在你這端包一層 JSON 介面。

### 標頭與 Query 參數裡的 `{{變數}}` <a href="#template-variables" id="template-variables"></a>

標頭與 Query 參數的值可以引用對話情境變數（`{{contact_id}}`、`{{source_id}}`、`{{conversation_id}}` 等）與參數結構中宣告的參數 `{{參數名}}`，平台在每次呼叫時代入。可用的變數清單與代入規則，以使用者手冊的 [會被代換的 `{{…}}` 變數](https://docs.maiagent.ai/build/tools/setup_api_tool#template-variables) 為準。

`{{source_id}}` 是 ISV 最常用的一個：它是聯絡人的外部 ID，也就是你在 Web Chat 嵌入或聯絡人同步時給的 `sourceId`。你的系統只認自己的使用者 ID，把它放進標頭（例如 `X-User-Id: {{source_id}}`）或 query string，你的 API 就能以該使用者的身分查資料，不必在參數結構裡叫模型「猜」使用者是誰。

## 認證方式 <a href="#authentication" id="authentication"></a>

三種方式可以並用，優先序由低到高：

1. **工具層固定憑證**：`Authorization: Bearer ...` 或 `X-API-Key: ...` 寫在標頭，或把金鑰寫在 Query 參數。代表「整個組織」呼叫你的 API。儲存後在後台以 `****` 遮罩顯示，工具執行記錄與 API 請求記錄裡的 Query 參數值也會被遮掉。
2. **情境變數**：如上節，用 `{{source_id}}` 等把「是誰在問」帶給你的 API，由你這端做授權。憑證仍是工具層的。
3. **聯絡人層憑證（API Credential）**：為特定聯絡人綁一組標頭，呼叫時**覆蓋同名的工具層標頭**。適合每位終端使用者各自持有 token 的情境（你的系統要以使用者本人的身分呼叫，而不是以整合帳號）。

聯絡人層憑證由你的後端在使用者登入後寫入：

```http
POST /api/contacts/{contact_id}/api-credentials/
Authorization: Api-Key <組織 API 金鑰>
Content-Type: application/json

{
  "tool": "<工具 ID>",
  "headers": {
    "Authorization": "Bearer <該使用者在你系統的 token>"
  }
}
```

同一聯絡人對同一工具重送會更新既有憑證；token 到期時再打一次即可。憑證只在「該聯絡人的對話」裡生效，其他聯絡人呼叫同一工具仍走工具層憑證。

{% hint style="info" %}
把 token 寫進聯絡人憑證的時機，通常是 Web Chat 嵌入時的身分同步流程，見 [聯絡人身分同步與 Token 更新](/tech/serve/contacts/contact-credentials-sync.md)。
{% endhint %}

### 金鑰放哪裡、誰看得到 <a href="#secrets-and-masking" id="secrets-and-masking"></a>

平台沒有「密鑰變數」機制：`{{SECRET_XXX}}` 這類自訂名稱不會被代換，儲存工具時會被拒絕。金鑰請直接填在工具的標頭或 Query 參數，或寫進聯絡人層憑證；能代換的 `{{變數}}` 只有[上一節](#template-variables)列出的情境變數與參數名。

| 位置                       | 遮罩規則                                                              |
| ------------------------ | ----------------------------------------------------------------- |
| 工具編輯畫面                   | 標頭與 Query 參數的值儲存後只顯示前後各 4 個字元，中間以 `****` 取代；8 個字元以內的值整個顯示為 `****` |
| 模型上下文                    | 標頭與 Query 參數的值不會提供給模型；模型只看得到工具名稱、描述與參數結構                          |
| 工具執行記錄的 `requestHeaders` | 名稱符合平台敏感標頭清單的標頭，以及來自聯絡人層憑證的所有標頭，值顯示為 `***`。名稱比對不分大小寫              |
| 工具執行記錄的請求 URL 與回應        | 工具「Query 參數」設定的值不會出現在請求 URL；回應或錯誤訊息回顯這些值時，也會被替換成 `***`            |

平台敏感標頭清單是 `Authorization`、`Proxy-Authorization`、`Cookie`、`Set-Cookie`、`X-API-Key`、`Api-Key`、`X-Auth-Token`、`X-Access-Token`。其他名稱的自訂標頭（例如 `X-Partner-Secret`）在工具執行記錄中會以明文顯示，因此：

* 你能決定標頭名稱時，把金鑰放在上列名稱的標頭。
* 必須使用自訂標頭名稱（例如 `x-functions-key`）時，用 REST API 在該工具設定 `sensitiveHeaders`：標頭名稱的陣列，最多 50 個、每個最長 256 字元，比對不分大小寫，只有 API 工具接受這個欄位；後台的工具設定沒有這個欄位。設定後，既有的執行記錄在檢視時也會套用遮罩。讀取工具時另有唯讀欄位 `globalSensitiveHeaders`，回傳平台目前的敏感標頭清單。
* 平台清單不能由組織管理者修改；地端部署可用環境變數 `TOOL_API_SENSITIVE_HEADERS` 整個取代。
* 金鑰不要寫在 API URL 裡。URL（含 `?` 之後的查詢字串）會原樣出現在工具執行記錄。

畫面上的對應說明見使用者手冊 [誰看得到這些值](https://docs.maiagent.ai/build/tools/setup_api_tool#credential-visibility)。

## 對話附件如何傳給工具 <a href="#conversation-attachments" id="conversation-attachments"></a>

API 工具可以拿到使用者在對話中上傳的檔案，方式是**傳網址**：平台不會把檔案內容塞進請求，而是由模型把附件的下載網址填進你宣告的參數，你的 API 再自行下載。

運作方式：

1. 使用者上傳檔案後，模型會看到每個附件的 `id`、`filename` 與 `file`（下載網址）。圖片附件同樣帶有檔名與網址。
2. 你在參數結構宣告一個字串參數接收網址，模型呼叫工具時把對應附件的網址填進去。
3. 你的 API 以 `GET` 下載該網址。

```json
{
  "type": "object",
  "properties": {
    "file_url": {
      "type": "string",
      "description": "要處理的檔案下載網址。使用者上傳附件時，填入該附件的 file 網址，必須原樣複製，不可改寫"
    },
    "filename": {
      "type": "string",
      "description": "檔案名稱，含副檔名"
    }
  },
  "required": ["file_url"]
}
```

要點：

* **網址形式**：`file` 是平台的固定網址（`https://<API 網域>/api/media/...`），每次下載時轉址到當下有效的儲存空間簽章網址。你的下載程式要**跟隨 HTTP 轉址**；下載這個網址不需要帶 API 金鑰。
* **網址被模型改寫時平台會修正**：模型抄寫網址時偶爾會抄錯主機或協定。只要參數值裡的網址對得上這段對話的某個附件，平台送出請求前會換回正確的網址。對不上任何附件的網址維持原樣送出。
* **一次多個檔案**：把參數宣告成字串陣列（`"type": "array", "items": {"type": "string"}`），並在 `description` 說明每個元素是一個附件網址。
* **請求仍是 JSON**：Body 不支援 form-data 或二進位上傳（見[請求怎麼組](#request-assembly)），所以無法讓平台「把檔案本體」送給你的 API。
* **大小與格式**：能上傳哪些檔案、單檔多大，取決於對話平台的附件設定，見 [API 共通規範：上傳大小上限](/tech/api-integration/api-conventions.md#upload-limits)。你的 API 應自行檢查 `Content-Length` 與檔案類型。
* **保留期限**：對話附件有保留天數，到期後檔案會被刪除、網址失效。需要長期保存的檔案，請在工具被呼叫時就下載到你自己的儲存空間。保留規則見使用者手冊 [Credits 計費制度：對話附件儲存](https://docs.maiagent.ai/others/credits#attachment-and-derived-storage)。
* **參數型別**：參數支援 `string`、`number`、`integer`、`boolean`、`enum`、巢狀 `object` 與 `array`（見[參數結構](#parameters-schema)）；沒有專用的「檔案」型別，檔案一律以網址字串傳遞。

{% hint style="warning" %}
附件下載網址不需要驗證就能存取，知道網址的人都能下載。你的 API 不要把它寫進對外可見的日誌或回應，也不要轉交給第三方。
{% endhint %}

## 逾時、重試與 TLS <a href="#timeout-retry-tls" id="timeout-retry-tls"></a>

| 項目  | 行為                                                                                                                                        |
| --- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| 逾時  | 每次 HTTP 請求的逾時設定預設 30 秒（平台層設定，地端可調），不是整次工具呼叫的總時限。超時的呼叫記為 `timeout`，模型收到「API 請求超時」。                                                         |
| 重試  | 首次請求之外最多重試 3 次，共最多 4 次嘗試，退避等待為 1、2、4 秒。對包含逾時在內的請求錯誤與 `408`、`409`、`429`、`500`、`502`、`503`、`504` 重試；其他 4xx 不重試。                             |
| TLS | 一律驗證憑證鏈與主機名稱。若你的憑證鏈使用缺少 Subject Key Identifier 的舊版根憑證（部分本地 CA）而連不上，這項放寬只能由 MaiAgent 在系統管理端逐一工具或逐一網域開啟，後台的工具設定沒有這個選項，請聯繫 MaiAgent。自簽憑證不支援。 |

對你的 API 的意義：

* 寫入型端點要**冪等**。重試會讓同一筆 `POST` 送到兩次，請用參數裡的業務鍵（訂單編號、外部 ID）去重，或改回 `409` 讓平台重試時仍是同一筆。
* 整次工具呼叫會累積各次請求及退避時間；連續逾時時可能超過 120 秒，不能把 30 秒當成模型等待的總上限。查詢型端點請控制在幾秒內，長工作改成「建立任務→回任務 ID」再提供查詢工具。
* 對 `429` 回 `Retry-After` 沒有作用，平台固定退避；請以 3 次重試的量估算限流。

## 回應怎麼回給模型 <a href="#response-to-model" id="response-to-model"></a>

模型看到的是你的**回應 body 原文**，不解析結構：

| 情況              | 模型看到                                          |
| --------------- | --------------------------------------------- |
| 2xx             | body 原文（`response.text`）。空 body 會變成字串 `None`。 |
| 非 2xx（重試後仍失敗）   | `API 請求失敗` 開頭，接 `HTTP <狀態碼>: <body 原文>`。      |
| 逾時              | `API 請求超時` 開頭，接錯誤說明。                          |
| 連線錯誤、DNS、TLS 失敗 | `API 請求失敗` 開頭，接例外訊息。                          |
| 組織 Credits 不足   | 呼叫不會送出，模型收到額度不足的說明。                           |

回應超過模型 context window 的一定比例（預設 80%）時，平台保留頭尾兩段並在最前面加上「已截斷」提示；被截掉的是中間段。

因此你的 API 設計建議：

* **回精簡 JSON**。只回模型需要的欄位，避免整包 ORM 物件。一次列表控制在幾十筆內，多的提供分頁參數讓模型再查。
* **錯誤用可讀訊息**，不要只回錯誤碼。`{"error": "order_id A123 不存在，請確認編號"}` 比 `{"code": 40401}` 更能讓模型修正參數或向使用者說明。
* **4xx 是給模型的回饋**：參數缺漏或格式錯回 `400` 並說明是哪個欄位，模型有機會重新填參數再呼叫；不要用 `200` 包錯誤，模型會把它當成功結果整理給使用者。
* 回應裡若會回顯 query string（部分框架的錯誤頁會），平台會把工具設定的 Query 參數值遮掉再給模型，但標頭不會回顯到 body 的前提是你的 API 自己不把它印出來。
* 要讓模型能對使用者說「查無資料」，就明確回 `404` 加說明，而不是 `200` 空陣列。
* **回應裡的圖片網址**：你的 API 回傳圖片網址時，模型會把它以 Markdown 圖片語法（`![說明](https://…)`）寫進回覆，Web Chat 顯示時由**使用者的瀏覽器**直接向該網址抓圖。這是兩段獨立的請求：平台→你的 API（帶工具標頭與憑證），瀏覽器→圖片網址（不帶任何工具標頭）。因此圖片網址必須能從公網不經認證存取，且在對話存續期間持續有效；短效簽章網址（例如 Azure SAS）到期後，舊訊息裡的圖片會顯示失敗。詳見[建立對話與訊息：訊息內容中的圖片](/tech/api-integration/conversations-and-messages.md#images-in-content)。

## 記錄與除錯 <a href="#records-and-debugging" id="records-and-debugging"></a>

每次呼叫都會寫入一筆**工具執行記錄**，後台在「AgentOps → 工具執行記錄」，也可用 API 查：

```http
GET /api/tool-execution-records/?tool=<工具 ID>&status=failure&start_date=2026-09-01&end_date=2026-09-11
Authorization: Api-Key <組織 API 金鑰>
X-Organization-Id: <組織 ID>
```

可用篩選：`tool`、`tool_type=api`、`status`（`success`、`failure`、`pending`、`timeout`）、`chatbot`、`start_date`、`end_date`、`query`（全文）。每筆記錄包含：

* 模型填的輸入參數 `inputParameters`，以及平台實際送出的 `requestMethod`、`requestHeaders`（敏感值遮罩）、`requestBody`。
* 回應原文 `outputResult`（截斷後的版本）或 `errorMessage`。
* 執行時間與狀態，關聯的訊息與 Agent。

讀取這個 API 需要成員具備 AgentOps 的「工具執行記錄」權限，組織 owner 不受限。

除錯順序建議：

1. 記錄裡的 `requestHeaders`／`requestBody` 是否是你預期的形狀。`{{變數}}` 沒被代入通常是變數名拼錯或參數結構裡沒有同名參數。
2. `errorMessage` 開頭是 `HTTP <碼>` 就是你的 API 回的錯，body 原文就在後面。
3. `status=timeout` 而你的 log 顯示已回應，代表回應時間超過 30 秒；先看你這端的 p95。
4. 模型「沒有呼叫工具」不會產生記錄。這時要改的是工具描述與參數 `description`，不是 API。

## 計費 <a href="#billing" id="billing"></a>

每次成功送出的 API 工具呼叫扣一次「API 工具呼叫」的 Credits，額度不足時呼叫不會送出。呼叫失敗或逾時的重試不另外計次。詳細單價依組織方案，見使用者手冊的 Credits 說明。

## 上線前檢查清單 <a href="#checklist" id="checklist"></a>

* [ ] 端點可從公網以 HTTPS 存取，憑證鏈完整。
* [ ] 查詢型端點 p95 在數秒內；寫入型端點冪等。
* [ ] 參數結構每個參數都有 `description` 與格式範例；`required` 只列真正必填的。
* [ ] 金鑰放在標頭或 Query 參數，不在參數結構裡。
* [ ] 需要「以使用者身分」呼叫時，決定用 `{{source_id}}` 還是聯絡人層憑證，並在你的後端完成授權。
* [ ] 錯誤回應是可讀訊息，`4xx` 說明缺什麼，不用 `200` 包錯誤。
* [ ] 回應精簡，列表有分頁。
* [ ] 用一個真實對話走完一次，到工具執行記錄確認送出的請求與回應都是預期的。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.maiagent.ai/tech/api-integration/api-tool-engineering-guide.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
