API Server 把 Hermes 的模型、Memory、Skills 和终端工具交给另一个系统调用。一次 curl 返回答案只证明网络接通;上线还必须证明错误 key 被拒绝、长任务可追踪、重复请求不会重复执行、浏览器 Origin 被限制,以及管理员能停止入口。
本课只在独立 api-lab-741 Profile 和 127.0.0.1:8642 上测试,固定输入为 API-CANARY-741。完成 A01–A14 前不绑定 0.0.0.0,也不接真实前端。
本站验证范围: 本页于 2026-09-09 核读 v0.21.1 / v2026.9.7 当前官方文档;本站未启动 Hermes API、调用模型、测试真实 token 或开放端口。
下载两份材料
运行卡只记录 key 的保管位置、owner 和轮换时间,不记录 key 本身。命令使用当前 shell 的 API_SERVER_KEY;录屏和 CI 日志应屏蔽 header。
先决定是否需要 API
| 需求 | 入口 |
|---|---|
| 人在终端里交互 | CLI / TUI |
| Telegram 等消息入口 | Messaging Gateway |
| 固定周期任务 | Cron |
| OpenAI-compatible 客户端、内部服务主动调用 | API Server |
| Desktop 或远程 Dashboard 后端 | hermes serve,这是另一套 JSON-RPC/WebSocket 服务,默认 9119 |
旧教程经常把 hermes serve 和本页 API Server 混为一谈。OpenAI-compatible API Server 是 Gateway 中的一个 platform,默认端口 8642,通过 API_SERVER_ENABLED=true 后运行 hermes gateway 启动。
先按 Profiles P01–P12 建立独立 Profile。Profile 会隔离 Hermes 状态,但不会自动限制 local terminal backend 的宿主机文件权限;API 调用可能触发完整工具集,因此要先缩小 toolset 与 cwd。
A01:建立独立 Profile 与固定环境
hermes profile create api-lab-741 --clone \
--description "Loopback API acceptance lab; no production writes"
hermes -p api-lab-741 doctor
hermes -p api-lab-741 dump
检查该 Profile 没有生产 Cron、消息渠道或不需要的写入 Skill。为它设置一个临时 terminal.cwd,只放无敏感测试文件。
A02:配置 loopback、key 与并发上限
在 ~/.hermes/profiles/api-lab-741/.env 写入:
API_SERVER_ENABLED=true
API_SERVER_HOST=127.0.0.1
API_SERVER_PORT=8642
API_SERVER_KEY=<由密码管理器生成的随机测试值>
API_SERVER_MODEL_NAME=api-lab-741
在同一 Profile 的 config.yaml 设置首轮并发上限:
gateway:
api_server:
max_concurrent_runs: 1
API key 在 loopback 上也必填。环境变量优先于 config.yaml;避免两个位置写不同 key/port 后排查错误。CORS 此时保持未配置。
A03:前台启动并确认监听主体
hermes -p api-lab-741 gateway
启动日志应显示 API Server 监听 http://127.0.0.1:8642。另开终端检查端口对应进程和 Profile,不能只看到“8642 已占用”就假定是本次 Gateway。
A04:区分 liveness 与 readiness
curl -i http://127.0.0.1:8642/health
curl -i http://127.0.0.1:8642/health/detailed \
-H "Authorization: Bearer $API_SERVER_KEY"
/health 是公开 liveness,预期 200 与 {"status":"ok"};它不运行 readiness 检查。/health/detailed 是鉴权后的有界状态,检查 active Profile 的 config、state DB、model、disk、Gateway、active runs 等,且不返回 key、路径、命令或原始错误。
Detailed readiness 即使 degraded 也使用 HTTP 200。自动监控必须解析顶层 status 与 readiness.checks,不能只看状态码。
A05:做三次鉴权负例
curl -i http://127.0.0.1:8642/v1/models
curl -i http://127.0.0.1:8642/v1/models \
-H "Authorization: Bearer definitely-wrong"
curl -i http://127.0.0.1:8642/v1/models \
-H "Authorization: Bearer $API_SERVER_KEY"
缺失与错误 bearer 必须是 401,正确 key 才能读取模型。若错误 key 得到业务数据,立即停止 Gateway,不继续后续测试。
A06:先发现能力,再写客户端
curl -sS http://127.0.0.1:8642/v1/capabilities \
-H "Authorization: Bearer $API_SERVER_KEY" | jq .
curl -sS http://127.0.0.1:8642/v1/models \
-H "Authorization: Bearer $API_SERVER_KEY" | jq .
curl -sS http://127.0.0.1:8642/v1/toolsets \
-H "Authorization: Bearer $API_SERVER_KEY" | jq .
Capabilities 是客户端的契约入口:确认 chat_completions、responses_api、run_submission、run_status、run_events_sse、run_stop 等实际 feature,再决定展示哪些按钮。/v1/models 只广告稳定 alias,不是 provider 全量目录;Hermes-aware UI 才使用 /api/model/options。
在运行卡抄录 API platform 最终解析出的 toolsets。它们可能包含 terminal/file 等完整能力,不能因请求长得像 OpenAI API 就把它当成纯文本模型。
A07:验证无状态 Chat Completions
curl -sS http://127.0.0.1:8642/v1/chat/completions \
-H "Authorization: Bearer $API_SERVER_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"api-lab-741","messages":[{"role":"user","content":"Reply with exactly API-CANARY-741 and nothing else."}],"stream":false}' | jq .
记录 HTTP 状态、response id、实际 model、唯一输出和 usage。Chat Completions 是无状态接口,每次都应携带完整 messages。裸 model 默认可能被当稳定 alias 并回退到 Gateway 默认;只有显式 provider 或启用 direct_model_requests 才能让 OpenAI-compatible 请求直接选择其他模型。
A08:验证 Responses 的服务端多轮
第一次请求:
curl -sS http://127.0.0.1:8642/v1/responses \
-H "Authorization: Bearer $API_SERVER_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"api-lab-741","input":"Remember only for this conversation: API-CANARY-741.","store":true}' | jq .
保存实际 resp_...,第二次用 previous_response_id 询问金丝雀。服务端会重建包含 tool calls/results 的链路,并把多轮记录为同一 Session。也可用固定 conversation 名让服务端自动链到该 conversation 最新 response。
Stored responses 在 SQLite 中跨 Gateway 重启保留,但最多 100 条并按 LRU 淘汰。不要把它当无限期业务数据库。DELETE /v1/responses/{id} 后再 GET 应按真实结果记录。
A09:创建可追踪 Run
为本次实验生成一个唯一、不可复用的幂等键,例如 api-lab-741-<uuid>:
curl -i -X POST http://127.0.0.1:8642/v1/runs \
-H "Authorization: Bearer $API_SERVER_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: api-lab-741-<uuid>" \
-d '{"input":"Reply with exactly API-CANARY-741 and nothing else.","session_id":"api-lab-741-session"}'
创建返回 run_id 与 started。用精确 ID 轮询:
curl -sS http://127.0.0.1:8642/v1/runs/<run-id> \
-H "Authorization: Bearer $API_SERVER_KEY" | jq .
追到 completed、failed 或 cancelled,再记录 output 与 usage。POST 接受不等于执行完成。
A10:证明相同重试不会创建第二个 Run
用 同一 Idempotency-Key 和完全相同 JSON 重发 A09。预期 HTTP 202、相同 run_id,并包含 Idempotency-Replayed: true。该 reservation 在启动工作前持久保存,Gateway 重启和 terminal state 后仍能重放;key 按认证 Profile/credential 隔离,并在最后状态更新后保留 24 小时。
随后用同一 key 改一个字符,预期 HTTP 409 与 idempotency_key_conflict。无 Idempotency-Key 的每次 POST 都创建新 run,因此客户端超时重试必须自己携带唯一键。
A11:验证 Events 与 Stop
创建一个足够长但无副作用的测试 run,然后:
curl -N http://127.0.0.1:8642/v1/runs/<run-id>/events \
-H "Authorization: Bearer $API_SERVER_KEY"
curl -sS -X POST http://127.0.0.1:8642/v1/runs/<run-id>/stop \
-H "Authorization: Bearer $API_SERVER_KEY"
Stop 先返回 stopping,只有 executor 退出后状态才变为 cancelled。不能在收到 stopping 时释放同一业务资源或宣告取消完成。SSE 断开不会取消 run;未消费的 event buffer 五分钟后过期,但仍可轮询状态、停止或审批。
A12:验证 CORS allowlist 正反例
只有浏览器必须直连时才配置:
API_SERVER_CORS_ORIGINS=http://localhost:3000
重启同一 Profile Gateway 后分别发送 allowlisted 与陌生 Origin 的 preflight:
curl -i -X OPTIONS http://127.0.0.1:8642/v1/runs \
-H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: authorization,content-type,idempotency-key"
curl -i -X OPTIONS http://127.0.0.1:8642/v1/runs \
-H "Origin: https://untrusted.invalid" \
-H "Access-Control-Request-Method: POST"
只有允许 Origin 应得到匹配的 Access-Control-Allow-Origin。CORS 管浏览器读取,不是鉴权;服务端脚本不受浏览器 CORS 保护,仍必须验证 bearer。
A13:验证并发上限和 Profile 路由
在 max_concurrent_runs: 1 时保持一个长 run 运行,再提交第二个,预期 HTTP 429 Too many concurrent runs (max 1)。客户端应退避,不能无界立即重试;0 会禁用上限,不适合作为首轮配置。
若启用 gateway.multiplex_profiles,共享 listener 通过 /p/<profile>/... 路由。每个 named Profile 必须使用自己的 API_SERVER_KEY;default key 对 named prefix 应返回 401。Run ID 也按 Profile 隔离,在另一个 Profile 查询返回 404。先在单 Profile 完成 A01–A12,再增加这组矩阵。
A14:撤销入口并复查端口
- 停止
api-lab-741Gateway。 - 确认 8642 不再监听,
/health不可达。 - 从 Profile 配置移除
API_SERVER_ENABLED和测试 CORS。 - 轮换或销毁测试 key。
- Export Profile,记录归档 SHA-256。
- 删除临时 Profile,再确认 default active。
如果对接系统已经保存 base URL/key,还要先撤销调用方配置,避免它持续产生 401 与重试流量。
上线必须填写的责任表
| 问题 | 必须有明确答案 |
|---|---|
| 谁能调用 | 服务身份、owner、key 轮换与离职交接 |
| Agent 能做什么 | Profile、cwd、Toolsets、Skills、审批规则 |
| 如何重试 | Idempotency-Key 生命周期与 409/429 处理 |
| 如何观察 | liveness、detailed readiness、run terminal state、usage |
| 如何停止 | stop 的最终 cancelled、Gateway 停止、key 撤销 |
| 浏览器是否直连 | 无需则关闭 CORS;需要则精确 Origin |
完成检查
- A01–A14 均有真实状态码、ID、JSON 字段或端口证据。
- 缺 key 与错误 key 都被拒绝,详细 readiness 没有泄露 Secret。
- Chat、Responses、Runs 三种接口的状态模型没有混用。
- 相同幂等请求返回同一 run,不同 payload 返回 409。
- Stop 被追踪到最终 cancelled,第二个并发请求得到 429。
- 非 allowlist Origin 没有得到跨域读取许可。
- Gateway 已停止,端口关闭,测试 key 与 Profile 已撤销。
需要把 API 对接 Open WebUI 时,应沿用同一份 A01–A14 表,而不是只填写 base URL。需要定时执行时,转到 Cron 生命周期实验,不要让外部客户端自己模拟 scheduler。