API 参考
五个路由组 + 统一鉴权 + SSE 订阅 — 面向集成方的紧凑端点手册。完整字段与 schema 见
/openapi.json(ARTIFACTFLOW_DEBUG=true时/docs渲染 Swagger UI)。
通用约定
- Base URL:
/api/v1/{group},group ∈auth / chat / artifacts / stream / admin - 鉴权:除
/health/*与/auth/login外全部要求Authorization: Bearer <JWT>;Admin 路由额外要求role=admin - JSON:请求与响应默认
application/json;文件上传用multipart/form-data - 时间:所有 timestamp 用 ISO-8601 字符串
- ID:所有资源 ID 用字符串(非自增数字),避免跨存储后端的整型语义差异
状态码约定
| 码 | 含义 | 触发 |
|---|---|---|
200 |
成功 | 正常响应 |
401 |
未鉴权 | 缺 token / token 失效 / 用户被禁用 |
403 |
权限不足 | 非 admin 访问 /admin/* 端点 |
404 |
资源不存在 | 也覆盖"跨用户访问" — 见 Design Decision |
409 |
冲突 | Lease 冲突 / interrupt 已解决 |
410 |
资源失效 | active-stream 指向的 stream 已过期 |
422 |
请求不合法 | 参数校验、文件过大、格式不支持 |
503 |
服务不可用 | Health ready 降级 |
404-not-403 原则
跨用户访问他人资源(GET /chat/{someone_elses_conv_id} 等)一律返回 404 而非 403,不泄露资源存在性。实现上 Repository 查询即带 user_id 过滤,"没找到"与"不是你的"在同一代码路径。
Auth
/api/v1/auth — 登录与用户管理(无自助注册)。
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| POST | /login |
— | 用户名密码换 JWT |
| GET | /me |
用户 | 当前用户信息 |
| POST | /users |
admin | 创建用户 |
| GET | /users |
admin | 列出用户(limit 1-200, offset, q 搜索) |
| PUT | /users/{user_id} |
admin | 更新 display_name / password / role / is_active |
POST /login
POST /api/v1/auth/login
{"username": "alice", "password": "..."}
200 OK
{
"access_token": "eyJhbGc...",
"token_type": "bearer",
"expires_in": 86400,
"user": {"id": "...", "username": "alice", "display_name": "Alice", "role": "user"}
}
- 失败统一返回 401(用户不存在 / 密码错 / 账号禁用),不区分原因
- 无
/refresh端点:token 过期后客户端需重新 login
POST /users
- 字段:
username,password,display_name,role∈user/admin 409用户名已存在;400非法 role
Chat 对话
/api/v1/chat — 所有对话、执行、消息树、permission 接续都在这一组。
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| POST | `` | 用户 | 发送消息、启动执行,返回 stream_url |
| GET | `` | 用户 | 列出自己的对话(limit 1-100, offset, q) |
| GET | /{conv_id} |
用户 | 对话详情含消息树 |
| DELETE | /{conv_id} |
用户 | 删除对话 |
| GET | /{conv_id}/active-stream |
用户 | 查询当前是否有运行中的 stream(用于重连) |
| POST | /{conv_id}/inject |
用户 | 运行中注入一条 user 消息 |
| POST | /{conv_id}/cancel |
用户 | 请求取消运行 |
| POST | /{conv_id}/resume |
用户 | Permission 审批后恢复运行 |
| GET | /{conv_id}/messages/{msg_id}/events |
用户 | 单个 message 的事件链(可观测性) |
Compaction 现为引擎内同步执行(单次 LLM 调用
input+output超阈值时触发),不再有手动触发端点;详见 engine.md → Compaction 机制。
POST `` — 发送消息
POST /api/v1/chat
{
"user_input": "...",
"conversation_id": "..." | null, // null → 新建对话
"parent_message_id": "..." | null // 可指定分支父节点
}
200 OK
{
"conversation_id": "...",
"message_id": "...",
"stream_url": "/api/v1/stream/{message_id}"
}
409 Conflict:该对话已有执行占用 lease — 客户端应先cancel或等前一次结束404:conversation_id存在但不属于当前用户(见 404-not-403)- 调用方拿到
stream_url后必须立即GET,否则 Transport 的 pending TTL 到期会清理
POST /{conv_id}/inject — 运行中注入
POST /api/v1/chat/{conv_id}/inject
{"content": "补充一条要求"}
200 OK
{"message_id": "...", "stream_url": "/api/v1/stream/{original_msg_id}"}
- 注入的消息不会新建
Message行,仅作为queued_message事件持久化 409:对话不在 interactive 状态(引擎已退出或未启动)stream_url指向原执行的 stream — 客户端无需重新订阅
POST /{conv_id}/cancel
- 设置取消标志,引擎在下一检查点 emit
cancelled并终止;并非立即生效 409:对话无运行中执行
POST /{conv_id}/resume — Permission 接续
POST /api/v1/chat/{conv_id}/resume
{"message_id": "...", "approved": true, "always_allow": false}
200 OK
{"stream_url": "/api/v1/stream/{message_id}"}
404:message 或 interrupt 不存在409:interrupt 已解决(超时、被 cancel 唤醒、或已被处理)stream_url与原POST /chat返回相同,继续订阅即可
GET /{conv_id}/active-stream
- 返回
{"conversation_id", "message_id", "stream_url"} 404:当前无运行中执行410:有执行但对应 stream 已过EXECUTION_TIMEOUT失效
GET /{conv_id}/messages/{msg_id}/events
- Query:
event_type(可选过滤) - 返回该消息的完整事件链(不含
llm_chunk,见 ../architecture/observability.md) - 用户侧可观测端点;Admin 版本见下方
/admin/conversations/{id}/events
Artifacts
/api/v1/artifacts — 上传、读取、导出、版本管理。
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| POST | /upload |
用户 | 新建对话并上传首个 artifact |
| GET | /{session_id} |
用户 | 列出 session 下所有 artifacts |
| POST | /{session_id}/upload |
用户 | 向已有 session 追加 artifact |
| GET | /{session_id}/{artifact_id} |
用户 | 获取内容 + 版本列表 |
| GET | /{session_id}/{artifact_id}/versions/{version} |
用户 | 获取指定版本 |
| GET | /{session_id}/{artifact_id}/export?format=docx |
用户 | 导出为 DOCX |
上传约定
multipart/form-data,字段名file- 大小上限:
config.MAX_UPLOAD_SIZE(环境可配,默认见 deployment.md) 422触发:超限、格式不支持- 走
create_from_upload路径绕过 write-back cache,直接 commit 到 DB(见 ../architecture/artifacts.md)
读取的即时性
GET /{session_id}和GET /{session_id}/{artifact_id}会合并 DB + 当前 engine 的内存缓存,返回最新快照GET .../versions/{version}与GET .../export只读 DB:执行中未 flush 的中间版本返回404
Stream (SSE)
/api/v1/stream — 订阅某次执行的事件流。
| 方法 | 路径 | 鉴权 | 说明 |
|---|---|---|---|
| GET | /{stream_id} |
用户 | SSE 长连接;stream_id == message_id |
请求头:
Authorization: Bearer <JWT>—— 强制;不支持 query tokenLast-Event-ID: <entry_id>—— 可选;Redis transport 下实现续传,InMemory 模式忽略
响应:
Content-Type: text/event-streamCache-Control: no-cacheX-Accel-Buffering: no
事件格式(多行,空行分隔):
event: tool_complete
id: 1710000003456-0
data: {"type":"tool_complete","timestamp":"...","agent":"search_agent","data":{...}}
心跳以 SSE comment 发送(: ping\n\n);间隔 SSE_PING_INTERVAL(默认 15s)。
终止条件:收到 complete / cancelled / error 后连接关闭。
事件类型完整列表与 data 字段契约见 ../architecture/observability.md → 事件目录。Transport 细节与断线续传见 ../architecture/streaming.md.
Admin
/api/v1/admin — 全部端点 role=admin,非 admin 返回 403。
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /conversations |
全站对话列表 + 活跃标记 |
| GET | /conversations/{conv_id}/events |
按 message 分组的事件时间线 |
GET /conversations
Query:limit (1-100, 默认 20), offset, q (title 搜索), user_id (按 owner 过滤)
响应:
{
"conversations": [
{
"id": "...", "title": "...", "user_id": "...", "user_display_name": "...",
"message_count": 42, "is_active": true,
"created_at": "...", "updated_at": "..."
}
],
"total": 120,
"has_more": true
}
is_active 实时来自 RuntimeStore(见 ../architecture/observability.md → Admin API)。
GET /conversations/{conv_id}/events
见 ../architecture/observability.md → Admin API.
Health
src/api/main.py,无鉴权(故意),供 LB / K8s 探针。
| 方法 | 路径 | 行为 |
|---|---|---|
| GET | /health/live |
始终 200 {"status":"ok"} |
| GET | /health/ready |
DB + Redis 连通性检查;全通过 200,任一失败 503 |
详见 ../architecture/observability.md → 健康检查.
Design Decisions
为什么 /chat 是资源路径而非 /conversations
conversation是 DB 名词,但对外语义是"对话"而非"会话记录";/chat读起来即动作也即资源- 保持
/chat、/chat/{id}/inject、/chat/{id}/cancel的一致命名 — action 不单独用PATCH /conversations/{id} - 内部仍使用
conversation_id字段名,命名空间与 URL 解耦
为什么没有 /auth/register / /auth/refresh
- 产品定位是团队内 SaaS,用户由 admin 主动创建;开放自助注册会放大滥用面
- JWT 直接用较长
expires_in(默认 86400s),过期重新 login — 省掉 refresh token 的存储与撤销复杂度 - 有强制失效需求可由 admin 通过 PUT
/users/{id}设is_active=false实现
为什么 Stream 用 stream_id == message_id
- 一次执行对应一个 message,ID 天然一对一,省去映射表
- 重连时客户端无需保存独立的
stream_id,只要记得上次的message_id - 权限校验天然走"这个 message 是不是你的"
为什么 Inject / Cancel / Resume 都走 POST /chat/{id}/...
- 都是对"运行中的执行"的副作用动作,资源定位都是
conv_id - 副作用天然非幂等(inject 会排队、cancel 会设标志),POST 语义契合
- 统一的路径 prefix 让前端 api client 可以共享 conv 级别的 wrapper