使用手冊

API 快速上手

系統生產者(業務系統、排程工作、HiZex Avatar)以 Producer API 建立訊息意圖,或以簽章事件告訴 Herald「發生了什麼」。OpenAPI 在 https://hizexai.com/docs(on-prem 為您的 api 網域 /docs)。

1. 取得 producer key

由系統管理員為您的單位發放 per-unit credential。格式 hub_<id>_<secret>,只在建立時顯示一次;伺服器只存 sha256。credential 限定可用的分類、受眾維度、是否可個別指定(individual)。

Authorization: Bearer hub_academic_4d8b1e6c2a9f7035…

2. 建立訊息意圖

POST https://hizexai.com/v1/messages
Authorization: Bearer hub_…
Idempotency-Key: pay-2027-09-30-batch-1        # 必填;每單位一個命名空間
Content-Type: application/json

{
  "categoryId": "payment_reminder",
  "templateId": "tpl-payment-due", "variables": { "due": "9/30" },
  "audience": { "kind": "query", "classIds": ["CS-2A", "CS-2B"] },
  "scheduleAt": "2027-09-23T08:30:00+08:00",
  "expiresAt":  "2027-09-30T23:59:00+08:00",
  "kind": "personal"
}

→ 201 { "id": "01J…", "state": "draft", "created": true }
   重放同一 Idempotency-Key → 200 { …, "created": false }
內容
templateId + variablesmessages: CanonicalMessage[] 二選一。
受眾
{kind:"all"}{kind:"query", roles?, orgUnits?, classIds?, groups?, tags?, history?, exclude?}{kind:"individual", uids:[…]}(僅有 individualTargeting 的 credential;每次寫稽核)。
管道
channelPlanOverride 只能縮不能放大。
unitId
credential 固定為自己的單位;session 使用者屬多單位時必填。

3. dry-run

POST /v1/messages/{id}/dry-run

{
  "recipients": { "total": 1268, "byChannel": { "line": 1190, "email": 78 },
                  "excluded": { "noConsent": 12, "noAddress": 31, "preferenceOff": 44, "blocked": 0 } },
  "quota": { "line": { "estimatedPushes": 1190, "unitRemaining": 1980, "poolRemaining": 14200, "wouldExceed": false } },
  "violations": []
}

只回數字,從不列名單。violations 非空時 submit 會以 400 content_violation 拒絕。

4. submit 與 approve

POST /v1/messages/{id}/submit     → { "state": "submitted" }   # 存 dry-run 快照
POST /v1/messages/{id}/approve    → { "approved": true }
   # producer key 呼叫 approve = 觸發自動核准檢查:
   #   分類 approval_rule=auto + 模板已核准 + dry-run 不超額 → system:auto 核准
   #   否則 { "approved": false, "reason": "template_not_approved" | "quota" | … } 留在人審佇列
GET  /v1/messages/{id}            → 訊息 + dry-run 快照 + stats{sent, skipped{…}, failed} + approvals
POST /v1/messages/{id}/cancel     → 送出前取消,釋放預留

5. 系統事件(HMAC)

業務系統不選分類、不選受眾,只說「發生了什麼」;Herald 依事件綁定決定怎麼發。

POST https://hizexai.com/v1/events/academic
Content-Type: application/json
X-PU-Key-Id: academic-2027
X-PU-Timestamp: 1793470200
X-PU-Signature: v1=<hex HMAC-SHA256(secret, X-PU-Timestamp + "." + rawBody)>

{
  "eventId": "cls-chg-20271101-0042",
  "type": "class.room_changed",
  "occurredAt": "2027-11-01T02:10:00+08:00",
  "subjectUids": ["s1120001", "s1120002"],
  "data": { "course": "資料結構", "at": "2027-11-02T09:10", "from": "E301", "to": "E302" }
}

→ 202 { "accepted": true, "duplicate": false, "eventId": "…", "messageId": "01J…" }
// Node.js:產生簽章
import { createHmac } from 'node:crypto';
const ts = Math.floor(Date.now() / 1000).toString();
const rawBody = JSON.stringify(event);
const sig = createHmac('sha256', SECRET).update(ts + '.' + rawBody).digest('hex');
await fetch(HERALD + '/v1/events/academic', {
  method: 'POST',
  headers: { 'content-type': 'application/json', 'x-pu-key-id': KEY_ID,
             'x-pu-timestamp': ts, 'x-pu-signature': 'v1=' + sig },
  body: rawBody,                       // 簽章是對這個字串算的,不能再序列化一次
});
驗證規則失敗
三個 headerX-PU-Key-Id、X-PU-Timestamp、X-PU-Signature 缺一不可401 unauthenticated
金鑰key id 對應 EVENT_SIGNING_KEYS;可兩把並存供輪替401 unknown_key
簽章HMAC-SHA256(secret, ts.rawBody) hex,constant-time 比對401 bad_signature
時間|now − ts| ≤ 300 s401 stale_timestamp
重放同簽章 10 分鐘內 + (source, eventId) 唯一202 duplicate:true(與第一次相同答案)
受眾subjectUids ≤ 5,000;只保留認識的 uid413 too_many_subjects / 202 error:no_subjects
綁定(source, type) 需有事件綁定與模板202 error:no_binding / no_template(事件已收下留紀錄)
簽章正確的事件一律 202 收下並留紀錄——包括之後建立訊息失敗的情況(202 {error}),讓您補綁定後可以重送。

6. 其他常用端點

端點用途
GET /v1/categories啟用中的分類(layer、同意範圍、管道計畫、審核規則)
GET /v1/units/{id}單位、成員、本月配額帳本與門檻狀態
GET/POST /v1/templates單位模板;建立後需核准,自動核准規則才生效
GET/POST /v1/scenariosPUT /v1/scenarios/{id}劇本含節點與邊;伺服器驗證 DAG
POST /v1/patternsGET /v1/patterns/{id}/report由 pattern 設定產生劇本;報表只有數字
GET/POST /v1/groupsPOST /v1/groups/{id}/members群組與成員(靜態需 legalBasis;子集約束)
GET/POST /v1/schedulesonce / delay / recurring
PUT /v1/subjects/{uid}/addresses/{channel}Identity API:綁定成功後寫入地址(限行政單位 credential)
POST /v1/subjects/{uid}/consents同意(scope、版本、來源);撤回 DELETE
GET /v1/me/inboxGET/PUT /v1/me/preferences使用者端(LINE id token)
GET /v1/handoffs/{id}POST /v1/webhooks/avatarHiZex Avatar 整合
GET /v1/healthDB、各 adapter、integrations

7. 錯誤碼與限流

所有錯誤回 { error: { code, message, details? } },從不回 stack。

HTTPcode
400validation_error / idempotency_key_required / content_violation / missing_variable / audience_dimension_not_allowed / dag_invalid / fixed_category
401unauthenticated / invalid_credential / not_bound / bad_signature / stale_timestamp / unknown_key
403forbidden / unit_required / category_not_allowed / individual_targeting_disabled / identity_api_forbidden
409idempotency_mismatch / invalid_transition / quota_exceeded / self_approval / group_subset
413too_many_subjects(> 5,000)/ too_many_events(> 100)
429rate_limited(附 Retry-After)
502 / 503adapter 的 provider / retryable / not_configured

對外前綴(/v1/auth//v1/events//v1/subjects//v1/me/)共用滑動視窗 60 次/分鐘;回應帶 x-ratelimit-limit / x-ratelimit-remaining