# HTTP API

通过 HTTP API 把一个工作流（AI 员工）集成到你的外部系统。需先在开发者中心「API 集成」栏目为该工作流创建 API Key，并在请求头携带 `Authorization: Bearer`。

- 同步调用：阻塞至执行完成，一次性返回最终结果（最长约 300 秒）
- 流式调用（SSE）：逐 token 实时输出，适合实时展示
- 两者都可选传 `session_id` 续接历史，实现多轮对话；执行中再发（带内容）即为「插话」
- 停止执行：随时终止正在运行的会话
- 会话列表 / 消息历史：只读查询接口，用于对账与回放
- 执行身份 = API Key 所属 owner，计费归 owner；调用方无法越权指定身份

## 基础地址

| 区域版本 | Base URL |
| --- | --- |
| 国内版 | `https://app.circow.cn/flow-api/public/flow-run/{flowId}` |
| 海外版 | `https://app.circow.com/flow-api/public/flow-run/{flowId}` |

下文以 `{BASE}` 代指上表地址。`{flowId}` 为目标工作流的 UUID，在开发者中心「API 集成」栏目左侧工作流列表中获取（也是浏览器地址栏 flow 页面 URL 里的那个 UUID）。`{apiKey}` 为创建 Key 时一次性显示的明文，形如 `fk_xxxxxxxx`。

| 端点 | 方法 | 用途 |
| --- | --- | --- |
| `{BASE}` | POST | 同步调用 |
| `{BASE}/stream` | POST | 流式调用（SSE） |
| `{BASE}/upload` | POST | 文件 / 图片上传 |
| `{BASE}/sessions/{session_id}/stop` | POST | 停止执行 |
| `{BASE}/sessions` | GET | 会话列表 |
| `{BASE}/sessions/{session_id}/messages` | GET | 消息历史 |

## 鉴权

所有请求需在请求头携带 API Key。Key 与本工作流绑定，被禁用或不匹配将返回 401（服务端不区分「不存在 / 已禁用 / 不匹配」）。

```bash
Authorization: Bearer {apiKey}
```

## 请求体

同步与流式两个端点通用同一套请求体。

```json
{
  "input_value": "your input text",
  "session_id": null,
  "file_paths": []
}
```

### 字段说明

| 字段 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| input_value | string | 是 | 输入文本（用户提问 / 任务指令） |
| session_id | string \| null | 否 | 多轮对话：不传每次新建会话；传上次返回的 session_id 续接历史 |
| file_paths | string[] | 否 | 文件/图片 URL 列表，透传给工作流由 AI 读取；标准用法见「文件上传」 |

## 多轮对话

通过 `session_id` 续接历史，实现连续多轮对话。

- 不传 / null：每次新建独立会话，无上下文记忆
- 传新字符串（建议自生成 UUID，可**预持有**会话 ID 用于停止/插话；SSE 首帧 `event: start` 也会回传）：按该 ID 新建会话
- 传上次返回的 session_id：续接历史，连续多轮对话
- 归属校验：传入已存在的会话必须属于本 Key 的 owner 且属于本工作流，否则返回 403
- 会话执行中再次调用（同步 / SSE 均可）：**带内容 → 自动转为「插话」投递**（同步返回 `status=delivered`，SSE 回一帧 `event: delivered` 后结束），agent 在当前回合内消费，回复出现在**原会话**的输出流/历史中；**空内容 → 409**。一个会话同一时刻只有一条输出流，插话不会另起新流

两个端点的响应都会回传最终使用的 `session_id`，下次续接时原样回传即可。

---

## 1. 同步调用

`POST {BASE}`

阻塞直到工作流跑完，一次性返回最后一条回复（最长约 300 秒）。

```bash
curl -X POST "{BASE}" \
  -H "Authorization: Bearer {apiKey}" \
  -H "Content-Type: application/json" \
  -d '{"input_value": "Hello World", "session_id": null}'
```

```python
import requests

resp = requests.post(
    "{BASE}",
    headers={
        "Authorization": "Bearer {apiKey}",
        "Content-Type": "application/json",
    },
    json={"input_value": "Hello World"},  # multi-turn: add "session_id"
    timeout=310,
)
resp.raise_for_status()
data = resp.json()
print(data["session_id"], data["status"], data["result"])
```

### 响应

```json
{
  "session_id": "...",
  "status": "completed",
  "result": "...",
  "error": null
}
```

`status` 取值：`completed`（跑完）/ `stopped`（被停止，result 为已产出部分）/ `delivered`（插话已入箱，立即返回）/ `running`（超 3600 秒软上限仍在后台执行）。

## 2. 流式调用 (SSE)

`POST {BASE}/stream`

逐 token 实时推送，不设超时（工作流内工具可能执行很久），收到 `done` 或 `error` 事件即结束。注意浏览器原生 `EventSource` 只支持 GET、不支持自定义请求头，无法用于本端点；请在服务端调用，或浏览器端用支持 POST+SSE 的库（如 `@microsoft/fetch-event-source`）。

### 请求头

| Header | 值 | 说明 |
| --- | --- | --- |
| Authorization | `Bearer {apiKey}` | 必填 |
| Content-Type | `application/json` | 必填 |
| Accept | `text/event-stream` | 建议带上 |

### SSE 事件流

响应 `Content-Type: text/event-stream`，每个事件为 `event: <类型>` + `data: <JSON>` 两行加空行。

```
event: start     data: {"session_id": "..."}
event: chunk     data: {"content": "delta text", "message_id": "..."}
event: done      data: {"session_id": "..."}
event: delivered data: {"session_id": "...", "mail_id": "..."}
event: error     data: {"session_id": "...", "error": "..."}
```

- `start`：首帧回传本次会话 ID——停止执行、插话、查询历史的凭据
- `chunk`：增量正文（只含正文，不含 reasoning 思考过程）。顺序拼接所有 chunk 的 `content` 即完整回答。多轮 Agent（agent→tool→agent）会产生多条 assistant 消息，`message_id` 不同；需要分段展示时按 `message_id` 分组，否则可忽略
- `done`：正常结束 / 被停止，收到即关闭连接
- `delivered`：目标会话正在执行中且本次带内容——输入已作为插话投递，本流随即结束；回复出现在原会话输出中
- `error`：执行出错，收到即流结束

### 请求示例

```bash
curl -N -X POST "{BASE}/stream" \
  -H "Authorization: Bearer {apiKey}" \
  -H "Content-Type: application/json" \
  -H "Accept: text/event-stream" \
  -d '{"input_value": "Hello World", "session_id": null}'
```

```python
import json, requests

with requests.post(
    "{BASE}/stream",
    headers={
        "Authorization": "Bearer {apiKey}",
        "Content-Type": "application/json",
        "Accept": "text/event-stream",
    },
    json={"input_value": "Hello World"},
    stream=True,
) as resp:
    resp.raise_for_status()
    event = None
    for line in resp.iter_lines(decode_unicode=True):
        if line.startswith("event:"):
            event = line[6:].strip()
        elif line.startswith("data:"):
            data = json.loads(line[5:].strip())
            if event == "chunk":
                print(data["content"], end="", flush=True)
            elif event == "done":
                print("\nsession_id:", data["session_id"]); break
            elif event == "error":
                print("\nerror:", data["error"]); break
```

## 3. 文件 / 图片上传

`POST {BASE}/upload`

先把文件上传到本接口换取可公开访问的 URL，再把该 URL 放进请求体的 `file_paths` 数组，与 `input_value` 一起发送，让 AI 读取文件 / 图片。

```bash
# 1) Upload file, get public_url
curl -X POST "{BASE}/upload" \
  -H "Authorization: Bearer {apiKey}" \
  -F "file=@/path/to/image.png"

# 2) Put public_url into file_paths to ask
curl -X POST "{BASE}" \
  -H "Authorization: Bearer {apiKey}" \
  -H "Content-Type: application/json" \
  -d '{"input_value": "What is in this image?", "file_paths": ["https://.../image.png"]}'
```

### 响应

```json
{
  "public_url": "https://.../xxx.png",
  "object_key": "...",
  "filename": "test.png",
  "content_type": "image/png",
  "size": 1475,
  "expires_at": 1781695551,
  "attachment_ttl_days": 30
}
```

### 说明

- multipart 字段名为 `file`，单文件上限 40 MB，上传后的 URL 有效期 30 天（`expires_at` 为 Unix 秒）
- `file_paths` 可放多个 URL，一次提问携带多个文件
- 也可直接传你自己的公网可访问 URL，不强制使用本上传接口
- 图片 / 文档由 AI 端按需拉取读取，请确保 URL 在调用期间可访问

## 4. 会话列表

`GET {BASE}/sessions`

分页列出本工作流的全部会话（按创建时间倒序），用于外部系统对账：拿 run 返回的 session_id 查询执行状态。包含所有来源的会话（网页端、API 调用、定时任务等）。

| Query 参数 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| page | int | 1 | 页码，从 1 开始 |
| page_size | int | 50 | 每页条数，上限 500 |

```bash
curl "{BASE}/sessions?page=1&page_size=50" \
  -H "Authorization: Bearer {apiKey}"
```

### 响应

```json
{
  "total": 123,
  "page": 1,
  "page_size": 50,
  "sessions": [
    {
      "id": "session-uuid",
      "name": "Hello World",
      "status": "completed",
      "created_at": "2026-08-19T02:00:00Z",
      "updated_at": "2026-08-19T02:01:00Z"
    }
  ]
}
```

## 5. 消息历史

`GET {BASE}/sessions/{session_id}/messages`

分页获取某个会话的消息记录（时间正序，不含隐藏消息）。默认每页 1000 条，`has_more=true` 时翻下一页。

| Query 参数 | 类型 | 默认 | 说明 |
| --- | --- | --- | --- |
| page | int | 1 | 页码，从 1 开始 |
| page_size | int | 1000 | 每页条数，上限 5000 |

| Path 参数 | 说明 |
| --- | --- |
| session_id | 会话 ID（来自 run 响应或会话列表） |

```bash
curl "{BASE}/sessions/SESSION_ID/messages?page=1&page_size=1000" \
  -H "Authorization: Bearer {apiKey}"
```

### 响应

```json
{
  "session_id": "SESSION_ID",
  "total": 2,
  "page": 1,
  "page_size": 1000,
  "has_more": false,
  "messages": [
    { "id": "msg-uuid-1", "role": "user", "content": "Hello World", "type": "ui_message", "created_at": "2026-08-19T02:00:00Z" },
    { "id": "msg-uuid-2", "role": "assistant", "content": "Hi! How can I help?", "type": "ui_message", "created_at": "2026-08-19T02:00:05Z" }
  ]
}
```

## 6. 停止执行

`POST {BASE}/sessions/{session_id}/stop`

停止正在执行的会话（无请求体）。仅 running 会话执行停止（撤销执行任务 + 会话置 stopped）；其他状态返回 noop、不改动终态（幂等可重试）。停止后复用同一 `session_id` 再调 run 即可续聊。

```bash
curl -X POST "{BASE}/sessions/SESSION_ID/stop" \
  -H "Authorization: Bearer {apiKey}"
```

### 响应

```json
{
  "session_id": "...",
  "status": "stopped",
  "session_status": "stopped",
  "revoked_tasks": 1
}
```

- `status=stopped`：已停止。原 SSE 流随即收到 `done`；同步调用返回 `status=stopped` 及已产出的部分结果
- `status=noop`：会话不在执行中（completed/stopped/error 等），`session_status` 为当前实际状态
- 404：session 不存在。刚发起执行后的毫秒级窗口内可能出现，短暂重试即可
- 作用域：owner 自己在网页端对同一工作流正在运行的会话，同样会被本端点停止——把 Key 交给第三方系统时请知悉
- 停止前已投递、尚未被消费的插话信件会保留，下一回合执行时一并消费

---

## 错误码

| 状态码 | 说明 |
| --- | --- |
| 400 | 请求非法（如上传文件为空），或同步调用鉴权通过但执行失败（`detail` 为错误原因） |
| 401 | API Key 缺失 / 无效 / 已禁用 / 与工作流不匹配 |
| 403 | 无权执行此工作流，或 session_id 不归属本 Key |
| 404 | 工作流不存在（消息历史 / 停止接口：session 不存在） |
| 409 | 会话正在执行中且本次请求无内容（无法作为插话投递），稍后重试 |
| 413 | 上传文件超过 40 MB 上限 |
| 200 + event:error | 流式端点：鉴权通过但执行出错（模型不可用、运行异常等） |

错误响应体为 JSON，`detail` 字段为人类可读的错误说明。
