22 KiB
TH Hotel SuperAgent API 对接契约
文档信息
| 项目 | 内容 |
|---|---|
| 文档版本 | 0.3 |
| 日期 | 2026-07-09 |
| 状态 | 已增加邮件会话任务和受控正文查询接口 |
| 适用范围 | SuperAgent 调用本系统查询上下文、查询邮件会话、提交 AI 任务结果 |
| 主要读者 | SuperAgent 对接方、后端、测试、运维 |
联调参数区
以下参数供 SuperAgent 联调时手动修改。该区块只用于 dev/test 联调,不作为生产 Secret 管理方式。
| 参数 | 当前联调值 | 中文说明 |
|---|---|---|
TH_HOTEL_API_BASE_URL |
http://8.138.234.141:18087 |
本系统后端基础地址;本地联调用 8080,部署环境改为实际网关或服务地址。 |
HOTEL_ID |
HOTEL-DEV |
当前 dev profile 下 AgentBus 入库默认酒店 ID;调用五个 SuperAgent 接口时均应传入请求体 hotel_id。 |
SUPERAGENT_CLIENT_ID |
superagent-debug |
SuperAgent 调用方 ID,对应 Header X-TH-Hotel-SuperAgent-Client-Id。 |
SUPERAGENT_HMAC_SECRET |
th-hotel-superagent-debug-20260709-change-before-prod |
dev/test 联调临时 HMAC 密钥;生产上线前必须更换为新的高强度随机密钥。 |
生产注意:
- 生产环境必须更换
SUPERAGENT_HMAC_SECRET,不得继续使用上述联调临时密钥。 - 生产密钥不得写入 SuperAgent skill 文件、仓库文档、前端代码、镜像或普通日志,只能通过部署 Secret / 环境变量注入。
- 本系统后端按 profile 优先读取环境专属密钥:dev 使用
SUPERAGENT_DEV_TASK_RESULT_HMAC_SECRET,test 使用SUPERAGENT_TEST_TASK_RESULT_HMAC_SECRET,prod 使用SUPERAGENT_PROD_TASK_RESULT_HMAC_SECRET;旧通用变量SUPERAGENT_TASK_RESULT_HMAC_SECRET仅作为兼容兜底。当前查询接口和任务结果通知接口共用同一个 HMAC secret。
1. 基础约定
请求地址先使用占位符:
{TH_HOTEL_API_BASE_URL}
上线或联调时由环境提供实际域名,例如 UAT、生产内网域名或 API Gateway 地址。本文所有接口均为 SuperAgent 到本系统的服务到服务调用,不给前端直接调用。
2. 通用 HMAC 鉴权
查询接口和任务结果通知接口使用同一套 HMAC-SHA256 规则。
2.1 通用 Header
| Header | 是否必填 | 中文说明 |
|---|---|---|
Content-Type |
是 | 固定 application/json |
X-TH-Hotel-SuperAgent-Client-Id |
是 | SuperAgent 调用方客户端 ID |
X-TH-Hotel-SuperAgent-Timestamp |
是 | UTC ISO-8601 时间,例如 2026-07-08T01:30:00Z |
X-TH-Hotel-SuperAgent-Nonce |
是 | 每次请求唯一随机值,用于防重放 |
X-TH-Hotel-SuperAgent-Signature |
是 | HMAC 签名,格式 sha256=<lowercase-hex> |
X-TH-Hotel-Request-Id |
否 | 调用方请求 ID,用于日志串联 |
X-TH-Hotel-AI-Trace-Id |
否 | AI 运行链路 ID,查询接口会原样带回 trace_id |
2.2 签名串
签名串使用接口 path,不包含域名、query string 或 fragment。
POST
<request_path>
<X-TH-Hotel-SuperAgent-Timestamp>
<X-TH-Hotel-SuperAgent-Nonce>
<X-TH-Hotel-SuperAgent-Client-Id>
<lowercase-hex-sha256-of-raw-body>
签名算法:
signature = HMAC_SHA256(SUPERAGENT_HMAC_SECRET, canonical_string)
Header 写法:
X-TH-Hotel-SuperAgent-Signature: sha256=<lowercase-hex-signature>
2.3 服务端校验顺序
- 校验请求体大小。
- 校验 HMAC 相关 Header 是否存在。
- 校验 timestamp 是否在允许时间窗口内。
- 计算原始请求体 SHA-256。
- 使用共享 secret 重新计算 HMAC。
- 常量时间比较签名。
- 校验并记录
client_id + nonce,防止重放。 - 查询接口校验
Content-Type是否为application/json。 - 鉴权和协议校验通过后再解析业务 JSON。
3. SourceMessage ID 口径
本系统存在两个容易混淆的 ID:
| 名称 | 中文说明 | 使用位置 |
|---|---|---|
| 外部来源消息 ID | AgentBus 邮件 payload 中的 source.external_message_id,SuperAgent / Main Agent 在最终 JSON 中原样带回为 source_message_id |
SuperAgent 任务结果通知接口入参和响应回显 |
| 内部 SourceMessage Inbox ID | platform_source_message_inbox.id,本系统数据库内部主键 |
workflow_* 表的 source_message_id 外键、前端和运维排查 |
SuperAgent 不应知道或依赖内部 SourceMessage Inbox ID。任务结果通知接口收到外部 source_message_id 后,后端使用 hotel_id + provider + channel + external_message_id 反查内部 Inbox 记录,再用内部 ID 写入业务表。
查询接口 1、2 在 SuperAgent 查询阶段不依赖当前邮件是否已经入库。若请求体兼容旧契约传入 source_message_id 或 source_event_index,第一版后端会接收但忽略,不校验它们的格式,也不把它们作为查询边界。
查询接口 3、4 面向已经入库的邮件会话:external_conversation_id 按 hotel_id + source_provider + source_channel + external_conversation_id 查询;source_message_id 表示外部来源消息 ID,可作为锚点反查该邮件所属会话。
4. 接口 1:查询订单上下文
4.1 请求
| 项目 | 内容 |
|---|---|
| Method | POST |
| URL | {TH_HOTEL_API_BASE_URL}/api/ai-query/v1/case-context |
| request_path | /api/ai-query/v1/case-context |
| Content-Type | application/json |
| 业务动作 | 只读查询,不创建任务、不修改订单、不写 OPERA |
4.2 请求体
{
"hotel_id": "<HOTEL_ID>",
"group_code": "GRP-001",
"confirmation_number": null,
"reservation_no": null,
"object_type_hint": "group_block",
"target_key_source": "body_current",
"body_thread_used_only_as_evidence": false
}
字段说明:
| 字段 | 是否必填 | 中文说明 |
|---|---|---|
hotel_id |
是 | 酒店上下文 ID |
group_code |
条件必填 | Group / Allotment 查询 key |
confirmation_number |
条件必填 | FIT Confirmation Number 查询 key |
reservation_no |
条件必填 | OPERA reservation no;当前系统无可靠表源,只传该字段时会返回人工复核原因 |
object_type_hint |
否 | 调用方推测的对象类型,只作为提示 |
target_key_source |
否 | key 来源,例如 body_current、body_thread_evidence |
body_thread_used_only_as_evidence |
否 | 历史线程 key 是否仅作为证据 |
group_code、confirmation_number、reservation_no 至少一个非空。当前稳定查询能力优先支持 group_code 和 confirmation_number。
全局上下文查询只依赖 hotel_id + 业务 key;source_message_id 和 source_event_index 不作为查询边界,传入时也不会影响查询结果。
4.3 成功响应
{
"success": true,
"request_id": "req-001",
"trace_id": "trace-001",
"data": {
"matched_order_records": [],
"pending_or_open_tasks": [],
"active_workflows": [],
"terminated_records": [],
"target_object_validation": {
"status": "none",
"matched_object_id": null,
"matched_object_type": null,
"can_create_new_booking_task": true,
"can_create_update_task": false,
"can_create_cancel_task": false,
"can_attach_voucher": false,
"can_attach_rooming_list": false,
"needs_manual_review_reason": null
},
"key_relationships": {
"group_code_and_confirmation_same_object": null,
"relationship_evidence": ""
}
},
"warnings": [],
"error": null
}
4.4 主要数据来源
| 返回字段 | 来源 |
|---|---|
matched_order_records[] |
workflow_reservation_order |
pending_or_open_tasks[] |
workflow_reservation_task + workflow_reservation_ai_transition |
terminated_records[] |
订单 ENDED / LOGIC_DELETED,任务 FAILED / COMPLETED |
active_workflows[] |
当前无独立 workflow 表,固定空数组 |
5. 接口 2:查询对象详情
5.1 请求
| 项目 | 内容 |
|---|---|
| Method | POST |
| URL | {TH_HOTEL_API_BASE_URL}/api/ai-query/v1/object-detail |
| request_path | /api/ai-query/v1/object-detail |
| Content-Type | application/json |
| 业务动作 | 只读查询对象详情,不创建任务、不修改订单、不写 OPERA |
5.2 请求体
{
"hotel_id": "<HOTEL_ID>",
"object_id": "ORDER:1900000000000000100",
"object_type": "group_block"
}
字段说明:
| 字段 | 是否必填 | 中文说明 |
|---|---|---|
hotel_id |
是 | 酒店上下文 ID |
object_id |
是 | 查询对象 ID,第一版只支持 ORDER:{order_id} |
object_type |
否 | 调用方对象类型提示,第一版不作为强校验 |
5.3 成功响应
{
"success": true,
"request_id": "req-002",
"trace_id": "trace-001",
"data": {
"object_id": "ORDER:1900000000000000100",
"object_type": "group_block",
"order_id": "1900000000000000100",
"order_key_type": "GROUP_CODE",
"group_code": "GRP-001",
"confirmation_number": null,
"reservation_no": null,
"block_id": null,
"temporary_order_code": "TMP-1900000000000000100",
"display_name": "GRP-001",
"status": "ACTIVE",
"source_message_id": "1900000000000000001",
"created_from_task_id": null,
"created_at": "2026-07-08T01:00:00Z",
"last_updated_at": "2026-07-08T01:10:00Z",
"arrival_date": null,
"departure_date": null,
"nights": null,
"guest_count": null,
"room_items": [],
"rate_code": null,
"rate_code_price": null,
"reservation_type": null,
"cancel_status": "not_cancelled",
"can_update": true,
"can_cancel": true,
"hard_validation_warnings": [
{
"code": "OPERA_PROJECTION_UNAVAILABLE",
"message": "当前系统尚未接入 OPERA 对象投影,日期、房型、房价等字段无法确认。"
}
]
},
"warnings": [],
"error": null
}
说明:接口 2 响应中的 source_message_id 当前是本系统内部 SourceMessage Inbox ID,用于对象溯源和排查;不要把该字段当作 SuperAgent 任务结果通知接口的外部 source_message_id 使用。
6. 接口 3:查询邮件会话下所有任务
6.1 请求
| 项目 | 内容 |
|---|---|
| Method | POST |
| URL | {TH_HOTEL_API_BASE_URL}/api/ai-query/v1/message-conversation/tasks |
| request_path | /api/ai-query/v1/message-conversation/tasks |
| Content-Type | application/json |
| 业务动作 | 只读查询邮件会话下任务,不创建任务、不修改订单、不写 OPERA |
6.2 请求体
按外部邮件会话 ID 查询:
{
"hotel_id": "<HOTEL_ID>",
"source_provider": "AGENTBUS",
"source_channel": "EMAIL",
"external_conversation_id": "thread-20260708-0001"
}
按外部来源消息 ID 作为锚点反查会话:
{
"hotel_id": "<HOTEL_ID>",
"source_provider": "AGENTBUS",
"source_channel": "EMAIL",
"source_message_id": "mail-20260708-0001"
}
字段说明:
| 字段 | 是否必填 | 中文说明 |
|---|---|---|
hotel_id |
是 | 酒店上下文 ID |
source_provider |
否 | 来源提供方,按会话 ID 查询和按 source_message_id 反查时都参与隔离,缺省为 AGENTBUS |
source_channel |
否 | 来源渠道,按会话 ID 查询和按 source_message_id 反查时都参与隔离,缺省为 EMAIL |
external_conversation_id |
条件必填 | 外部邮件会话 ID,对应 AgentBus source.external_conversation_id |
source_message_id |
条件必填 | 外部来源消息 ID,对应 AgentBus source.external_message_id,不是内部 Inbox ID |
external_conversation_id、source_message_id 至少一个非空。两者同时传入时,第一版以后端直接按 external_conversation_id 查询为准。
6.3 成功响应
{
"success": true,
"request_id": "req-003",
"trace_id": "trace-001",
"data": {
"hotel_id": "HOTEL-DEV",
"external_conversation_id": "thread-20260708-0001",
"task_count": 2,
"tasks": [
{
"task_id": "1900000000000000400",
"order_id": "1900000000000000300",
"external_source_message_id": "mail-20260708-0001",
"external_conversation_id": "thread-20260708-0001",
"source_received_at": "2026-07-08T01:00:00Z",
"source_event_index": 1,
"catalog_code": "S02",
"skill_id": "update_booking_amendment_skill",
"result_type": "normal_task",
"task_type": "Update Booking",
"system_task_type": "UPDATE_BOOKING",
"task_card_type": "UPDATE_BOOKING",
"task_subtype": "update_stay_dates",
"task_status": "PENDING_CONFIRM",
"queue_participation": true,
"execution_order": 1,
"parent_task_id": null,
"parent_source_event_index": null,
"linked_task_group_id": null,
"blocked_until_parent_completed": false,
"completed_at": null,
"task_created_at": "2026-07-08T01:01:00Z",
"task_updated_at": "2026-07-08T01:01:00Z"
}
]
},
"warnings": [],
"error": null
}
排序规则:
- 先按邮件
received_at正序。 - 同一封邮件下,再按任务
created_at正序。 - 若时间相同,再按
task_id正序稳定排序。
7. 接口 4:查询邮件会话下所有受控正文
7.1 请求
| 项目 | 内容 |
|---|---|
| Method | POST |
| URL | {TH_HOTEL_API_BASE_URL}/api/ai-query/v1/message-conversation/messages |
| request_path | /api/ai-query/v1/message-conversation/messages |
| Content-Type | application/json |
| 业务动作 | 只读查询邮件会话受控正文,不返回附件 URL 或原始未清洗 HTML |
7.2 请求体
请求体字段与接口 3 相同,可按 external_conversation_id 查询,也可按外部 source_message_id 锚点反查会话。
{
"hotel_id": "<HOTEL_ID>",
"source_provider": "AGENTBUS",
"source_channel": "EMAIL",
"source_message_id": "mail-20260708-0001"
}
7.3 成功响应
{
"success": true,
"request_id": "req-004",
"trace_id": "trace-001",
"data": {
"hotel_id": "HOTEL-DEV",
"external_conversation_id": "thread-20260708-0001",
"message_count": 2,
"messages": [
{
"external_source_message_id": "mail-20260708-0001",
"external_conversation_id": "thread-20260708-0001",
"sender_summary": "guest@example.test",
"subject": "Booking update",
"received_at": "2026-07-08T01:00:00Z",
"source_sent_at": "2026-07-08T00:59:00Z",
"text_body": "Please update arrival date...",
"html_body_sanitized": "<html><body>Please update arrival date...</body></html>",
"html_sanitize_required": true,
"html_render_mode": "SANITIZED_HTML"
}
]
},
"warnings": [],
"error": null
}
安全边界:
messages[]按邮件received_at正序返回。- 不返回
html_body原始未清洗 HTML。 - 不返回
attachments、inline_images、external_url、附件 URL 或 HTML 中的href/src外链属性。 - 后端读取正文时会写入 SourceMessage 原文访问审计。
8. 接口 5:SuperAgent 通知 AI 任务结果
8.1 请求
| 项目 | 内容 |
|---|---|
| Method | POST |
| URL | {TH_HOTEL_API_BASE_URL}/api/integrations/superagent/task-results |
| request_path | /api/integrations/superagent/task-results |
| Content-Type | application/json |
| 业务动作 | 接收 AI 任务结果,写入 AI 过渡层、订单、任务和任务卡 |
8.2 请求体
{
"hotel_id": "<HOTEL_ID>",
"source_message_id": "mail-20260708-0001",
"ai_task_results": [
{
"source_event_index": 1,
"catalog_code": "S01",
"skill_id": "S01_new_booking_skill",
"result_type": "normal_task",
"task_type": "New Booking",
"task_subtype": "new_fit_reservation",
"current_or_history": "current",
"case_keys": {
"group_code": null,
"confirmation_number": null
},
"visible_reason": "邮件正文包含新建预订请求。",
"relevant_message_excerpt": "Please create a new booking...",
"attachments": [],
"file_references": [],
"context_used": {},
"extracted_fields": {},
"manual_review": null,
"informational_message": null,
"additional_operations": [],
"idempotency_key": null
}
],
"extraction_warnings": []
}
字段说明:
| 字段 | 是否必填 | 中文说明 |
|---|---|---|
hotel_id |
是 | 酒店上下文 ID,用于反查 SourceMessage Inbox 幂等键 |
source_message_id |
是 | SuperAgent / Main Agent 原样带回的外部来源消息 ID,对应 AgentBus source.external_message_id,一次请求只能有一个 |
source_provider |
否 | 来源提供方,第一版缺省为 AGENTBUS |
source_channel |
否 | 来源渠道,第一版缺省为 EMAIL |
ai_task_results[] |
是 | AI 拆分出的任务结果列表,必须保留数组顺序 |
ai_task_results[].source_event_index |
是 | AI current 事件序号 |
ai_task_results[].catalog_code |
是 | Skill 目录代码 |
ai_task_results[].skill_id |
是 | Skill 标识 |
ai_task_results[].result_type |
是 | normal_task、manual_review、informational_message |
ai_task_results[].task_type |
是 | AI 原始任务类型 |
ai_task_results[].task_subtype |
否 | 业务动作 subtype |
ai_task_results[].case_keys |
否 | 订单关联候选键 |
ai_task_results[].extracted_fields |
否 | 业务字段主体 |
正式联调时,后端通过 hotel_id + source_provider(默认 AGENTBUS) + source_channel(默认 EMAIL) + source_message_id 查找 platform_source_message_inbox.external_message_id。如果没有找到,返回 SOURCE_MESSAGE_NOT_FOUND。本地旧夹具允许在缺少 hotel_id 时使用内部数字 SourceMessage ID,但该兼容路径不作为 SuperAgent 正式契约。
8.3 成功响应
{
"request_id": "req-003",
"source_message_id": "mail-20260708-0001",
"batch_id": "1900000000000000200",
"idempotent_replay": false,
"accepted_count": 1,
"items": [
{
"source_event_index": 1,
"array_index": 1,
"ai_transition_id": "1900000000000000250",
"order_id": "1900000000000000300",
"task_id": "1900000000000000400",
"system_task_type": "NEW_BOOKING",
"task_card_type": "NEW_BOOKING",
"task_status": "PENDING_CONFIRM",
"order_status": "TEMPORARY",
"execution_order": 1
}
],
"warnings": []
}
9. 错误响应
9.1 查询接口错误响应
{
"success": false,
"request_id": "req-001",
"trace_id": "trace-001",
"data": null,
"warnings": [],
"error": {
"code": "AUTH_SIGNATURE_INVALID",
"message": "签名校验失败。",
"details": {}
}
}
9.2 任务结果通知接口错误响应
{
"request_id": null,
"error_code": "AUTH_SIGNATURE_INVALID",
"message": "签名校验失败。",
"details": []
}
9.3 常见错误码
| 错误码 | HTTP 状态 | 中文说明 |
|---|---|---|
AUTH_HEADER_MISSING |
401 | HMAC 必要 Header 缺失 |
AUTH_HEADER_INVALID |
401 | HMAC Header 格式或长度无效 |
AUTH_TIMESTAMP_INVALID |
401 | timestamp 格式错误或超出时间窗口 |
AUTH_SIGNATURE_INVALID |
401 | 签名不匹配或服务端未配置 secret |
AUTH_NONCE_REPLAY |
409 | nonce 已被使用 |
REQUEST_BODY_TOO_LARGE |
413 | 请求体超过大小限制 |
REQUEST_CONTENT_TYPE_UNSUPPORTED |
415 | Content-Type 不是 application/json |
REQUEST_BODY_INVALID |
400 | JSON 不合法 |
QUERY_KEY_REQUIRED |
400 | 查询接口缺少可用业务 key |
MESSAGE_CONVERSATION_QUERY_KEY_REQUIRED |
400 | 会话查询缺少 external_conversation_id 或 source_message_id |
OBJECT_NOT_FOUND |
404 | 对象详情查询目标不存在 |
MESSAGE_CONVERSATION_NOT_FOUND |
404 | 外部邮件会话尚未写入 SourceMessage Inbox |
HOTEL_ID_REQUIRED |
400 | 五个 SuperAgent 接口缺少必填 hotel_id |
SOURCE_MESSAGE_NOT_FOUND |
404 | 任务结果通知或会话锚点引用的外部来源消息尚未写入 SourceMessage Inbox |
10. HMAC 上线配置
上线需要配置:
| 配置项 | 是否必填 | 中文说明 |
|---|---|---|
SUPERAGENT_DEV_TASK_RESULT_HMAC_SECRET |
dev 必填 | dev HMAC 共享密钥;查询接口和任务结果通知接口共用 |
SUPERAGENT_TEST_TASK_RESULT_HMAC_SECRET |
test 必填 | test HMAC 共享密钥;查询接口和任务结果通知接口共用 |
SUPERAGENT_PROD_TASK_RESULT_HMAC_SECRET |
prod 必填 | prod HMAC 共享密钥;生产不能为空,只能通过 Secret 注入 |
SUPERAGENT_TASK_RESULT_HMAC_SECRET |
兼容兜底 | 旧通用 HMAC 变量;优先使用环境专属变量 |
SUPERAGENT_TASK_RESULT_CLOCK_SKEW_SECONDS |
否 | 请求时间允许偏移,默认 300 秒 |
SUPERAGENT_TASK_RESULT_NONCE_TTL_SECONDS |
否 | nonce 防重放保存时间,默认 600 秒 |
SUPERAGENT_TASK_RESULT_MAX_BODY_BYTES |
否 | 请求体最大字节数,默认 1048576 |
上线注意事项:
- 本系统和 SuperAgent 必须配置同一个 HMAC secret。
- 生产 secret 只能放在部署平台 Secret 或环境变量中,不能写入仓库、镜像、前端配置或普通文档。
- SuperAgent 必须使用原始请求体计算 SHA-256,不能使用格式化后 JSON。
- HMAC canonical string 的第二行必须使用
request_path,例如/api/ai-query/v1/case-context。 - SuperAgent 每次请求必须生成全新的 nonce;同一个
client_id + nonce在 TTL 窗口内不能重复使用。 - 调用双方服务器时间必须同步,建议使用 NTP。
- 建议所有接口只暴露在 HTTPS 和可信网络边界内。
- 轮换 secret 时需要安排双写或短窗口切换;当前第一版后端只支持一个 secret,轮换窗口内需要协调发布顺序。