使用手冊
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 + variables或messages: 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, // 簽章是對這個字串算的,不能再序列化一次
});
| 驗證 | 規則 | 失敗 |
|---|---|---|
| 三個 header | X-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 s | 401 stale_timestamp |
| 重放 | 同簽章 10 分鐘內 + (source, eventId) 唯一 | 202 duplicate:true(與第一次相同答案) |
| 受眾 | subjectUids ≤ 5,000;只保留認識的 uid | 413 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/scenarios、PUT /v1/scenarios/{id} | 劇本含節點與邊;伺服器驗證 DAG |
POST /v1/patterns、GET /v1/patterns/{id}/report | 由 pattern 設定產生劇本;報表只有數字 |
GET/POST /v1/groups、POST /v1/groups/{id}/members | 群組與成員(靜態需 legalBasis;子集約束) |
GET/POST /v1/schedules | once / delay / recurring |
PUT /v1/subjects/{uid}/addresses/{channel} | Identity API:綁定成功後寫入地址(限行政單位 credential) |
POST /v1/subjects/{uid}/consents | 同意(scope、版本、來源);撤回 DELETE |
GET /v1/me/inbox、GET/PUT /v1/me/preferences | 使用者端(LINE id token) |
GET /v1/handoffs/{id}、POST /v1/webhooks/avatar | HiZex Avatar 整合 |
GET /v1/health | DB、各 adapter、integrations |
7. 錯誤碼與限流
所有錯誤回 { error: { code, message, details? } },從不回 stack。
| HTTP | code |
|---|---|
| 400 | validation_error / idempotency_key_required / content_violation / missing_variable / audience_dimension_not_allowed / dag_invalid / fixed_category |
| 401 | unauthenticated / invalid_credential / not_bound / bad_signature / stale_timestamp / unknown_key |
| 403 | forbidden / unit_required / category_not_allowed / individual_targeting_disabled / identity_api_forbidden |
| 409 | idempotency_mismatch / invalid_transition / quota_exceeded / self_approval / group_subset |
| 413 | too_many_subjects(> 5,000)/ too_many_events(> 100) |
| 429 | rate_limited(附 Retry-After) |
| 502 / 503 | adapter 的 provider / retryable / not_configured |
對外前綴(/v1/auth/、/v1/events/、/v1/subjects/、/v1/me/)共用滑動視窗 60 次/分鐘;回應帶 x-ratelimit-limit / x-ratelimit-remaining。