怡途Claw 文件 API 实战:用 POST /api/app/chat/instances/:id/files 给 AI Agent 上传 PDF 和文档,大文件走七牛直传,agent 生成的文件从 outbox 自动回捞,附 20MB 单文件与 500MB 总配额限制及真实报错排查。
部署完一个 AI Agent 实例(怎么起实例见《如何用 API 一键部署 AI Agent》),也跑通了流式对话(见《怡途Claw 流式对话 API 实战》),下一个真实需求几乎是必然的:让它读你自己的文件。客服场景要喂产品手册 PDF,分析场景要传一份客户名单,代码场景要丢几个源码文件进去。光靠每轮对话里粘文本,长文档塞不下、二进制文件没法传。
怡途Claw 给的入口是 /api/app/chat/instances/:id/files 这一组接口。这篇把"传文件给 agent、agent 再把生成的文件吐回来"这条链路拆成四步,每步带上真实字段和我踩过的坑。
一、把文件传进实例(小文件直传)
最直接的方式是 multipart 上传,字段名固定叫 file,可选带 sessionId 归到某个会话:
POST /api/app/chat/instances/ee0f78d0-83ec-4ad9-b7f6-0690e0416499/files
Authorization: Bearer <token>
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary
------WebKitFormBoundary
Content-Disposition: form-data; name="file"; filename="产品手册.pdf"
Content-Type: application/pdf
<二进制内容>
------WebKitFormBoundary
Content-Disposition: form-data; name="sessionId"
main
------WebKitFormBoundary--
成功返回这个文件 record:
{
"success": true,
"message": "文件上传成功",
"data": {
"id": "7a2c1f5e-3b4d-4e8a-9c1f-2d3e4f5a6b7c",
"instanceId": "ee0f78d0-83ec-4ad9-b7f6-0690e0416499",
"direction": "upload",
"source": "app",
"status": "available",
"originalName": "产品手册.pdf",
"mimeType": "application/pdf",
"sizeBytes": 4821034,
"containerPath": "/home/node/.openclaw/media/app-inbox/7a2c1f5e-3b4d-4e8a-9c1f-2d3e4f5a6b7c-产品手册.pdf",
"downloadUrl": "/api/app/chat/instances/ee0f78d0-83ec-4ad9-b7f6-0690e0416499/files/7a2c1f5e-3b4d-4e8a-9c1f-2d3e4f5a6b7c/download",
"createdAt": "2026-07-28T03:12:44.000Z"
}
}
几个字段值得看清:
direction: "upload"说明是用户上传的,区别于 agent 自己生成的(后文第四节讲)。containerPath是文件在容器里的落点,agent 实际就读这个路径。PDF 这类媒体文件落到/home/node/.openclaw/media/app-inbox/,普通文档落到/home/node/.openclaw/app-files/inbox/——平台按 mimeType 分流,媒体类会优先喂给图片/音视频分析工具。downloadUrl是回传给你用的下载入口,后面第三节细说。
文件传上去之后,下一次发消息时平台会把这个文件的 containerPath 注入到 agent 的上下文里(一段 [APP_FILE_CONTEXT] 指令),agent 就知道去哪读、文件叫什么、多大。图片附件更特殊:会同时以原生多模态图片消息发给模型,所以你传张截图问"这个报错怎么修",agent 是真看到了图,不是只能看个路径。
坑一:实例没运行,传不了
这个接口要求实例处于 running 状态、有 containerId。如果实例还没起来或被停了:
{
"statusCode": 400,
"message": "实例未运行,暂时无法上传文件",
"error": "Bad Request"
}
文件是要写进容器的,容器不存在自然没地方落。先确保实例 running(起实例见《如何用 API 一键部署 AI Agent》)再传。
坑二:单文件超 20MB 被拒
单文件大小上限由环境变量 APP_CHAT_FILE_MAX_BYTES 控制,默认 20MB,超过直接 400:
{
"statusCode": 400,
"message": "文件大小不能超过 20MB",
"error": "Bad Request"
}
20MB 够大多数文档和图片,但遇到大 PDF、长视频就得走第二节的大文件直传。注意这个 20MB 是单文件限制,跟下面的总配额是两码事。
坑三:总存储超 500MB
每个用户有总存储配额,按套餐的 fileStorageLimitMb 算,没订阅默认 500MB,超了会报:
{
"statusCode": 400,
"message": "文件存储空间不足。已使用 487.3MB,上限 500MB",
"error": "Bad Request"
}
这条 message 里会带当前已用量和上限,挺好用。想查实时用量调 GET /api/app/files/stats,返回 { usedBytes, limitBytes, fileCount }。配额是用户级共享的,不是单个实例独占。
二、大文件走七牛直传(upload-config + confirm)
单文件超 20MB,或者你就是不想让文件过一遍服务器中转,就走七牛对象存储直传,三步。
第一步,向后端要一个上传凭证:
POST /api/app/chat/instances/ee0f78d0-83ec-4ad9-b7f6-0690e0416499/files/upload-config
Authorization: Bearer <token>
Content-Type: application/json
{
"originalName": "培训视频.mp4",
"sizeBytes": 104857600
}
返回:
{
"success": true,
"data": {
"uploadUrl": "https://upload.qiniup.com",
"token": "前缀很长的一串七牛token",
"key": "openclaw/ee0f78d0-83ec-4ad9-b7f6-0690e0416499/7a2c1f5e-xxxx.mp4",
"fileId": "7a2c1f5e-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
}
第二步,拿这个 token 和 key 直接 PUT 到七牛的 uploadUrl,文件字节不经过你的服务器。
第三步,回到后端确认上传,落库生成文件 record:
POST /api/app/chat/instances/ee0f78d0-83ec-4ad9-b7f6-0690e0416499/files/confirm
Authorization: Bearer <token>
Content-Type: application/json
{
"fileId": "7a2c1f5e-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"key": "openclaw/ee0f78d0-83ec-4ad9-b7f6-0690e0416499/7a2c1f5e-xxxx.mp4",
"originalName": "培训视频.mp4",
"mimeType": "video/mp4",
"sizeBytes": 104857600
}
确认时后端会去七牛 HEAD 一下这个 key,拿到真实大小覆盖你传的 sizeBytes,所以客户端报的 size 不准也没事。确认完返回的 record 跟第一节一样,只是 storageType 变成了 "qiniu"。
坑四:七牛没配置,直接 404
直传这条链路依赖七牛对象存储。环境没配七牛时,upload-config 会返回:
{
"statusCode": 404,
"message": "七牛云存储未配置",
"error": "Not Found"
}
这种情况只能退回第一节的 multipart 直传(受 20MB 限制),或者让管理员配上七牛。注意 confirm 阶段如果七牛没配,报的是另一个 message:"七牛存储未配置"(400 BadRequest)——两个接口措辞略有差异,看到任一都指向同一个根因。
坑五:confirm 时 key 对不上 fileId
confirm 会校验你传的 key 里必须包含 upload-config 给的 fileId,防止串号:
{
"statusCode": 400,
"message": "文件 key 不匹配",
"error": "Bad Request"
}
别自己改 key,原样回传就行。
三、列出、下载和删除文件
列出某实例的文件:
GET /api/app/chat/instances/ee0f78d0-83ec-4ad9-b7f6-0690e0416499/files
Authorization: Bearer <token>
返回文件数组,direction 字段区分 upload(你传的)和 generated(agent 生成的)。调这个列表接口时,平台还会顺手扫一遍容器的 outbox 目录,把 agent 新生成的文件自动回捞上来——这就是为什么你传完文件再列,有时能看到一些自己没传的文件。
下载:
GET /api/app/chat/instances/ee0f78d0-83ec-4ad9-b7f6-0690e0416499/files/7a2c1f5e-3b4d-4e8a-9c1f-2d3e4f5a6b7c/download
Authorization: Bearer <token>
这里有个行为分叉:如果文件存七牛,接口直接 302 重定向到七牛的临时外链(record 里有 directUrl 字段),浏览器跟着跳就能下;如果存本地,接口直接流式吐字节,带 Content-Disposition: attachment 和正确的 Content-Type。客户端不用关心存哪,跟着 downloadUrl 走即可。
agent 刚生成的本地文件可能还没落盘完成,这时下载会报:
{
"statusCode": 404,
"message": "文件尚未准备完成,请稍后重试",
"error": "Not Found"
}
过几秒再下就好。
删除走用户级接口(注意是 /api/app/files/:fileId,不带实例 id):
DELETE /api/app/files/7a2c1f5e-3b4d-4e8a-9c1f-2d3e4f5a6b7c
Authorization: Bearer <token>
软删除,record 的 status 置为 deleted,存储侧文件也清掉,但数据库行留着。删完再列不会出现,下载会报 "文件不存在"。
用户级接口:跨实例管文件
上面带 instances/:id 的是实例级接口,文件绑死在某个实例上。还有一组用户级接口 /api/app/files/*,文件不绑实例(instanceId 为空),传完可以后续 attach 到任意实例的某条消息上。适合"先传一批资料进库,哪个实例要用再挂过去"的场景。两套接口字段和返回结构一致,区别只在绑不绑实例。
四、Agent 生成的文件怎么自动回到你手里
文件流转不是单向的。agent 在容器里跑工具,生成的文件(导出的报告、画好的图、跑完的代码产物)会写到固定目录 /home/node/.openclaw/app-files/outbox。你不用手动去容器里捞,平台在你调列表接口、或发消息触发同步时,会自动 docker exec 扫这个 outbox,把新文件下载回宿主机、算 sha256 去重、传七牛,落成一条 direction: "generated"、source: "agent" 的 record。
几个边界值得知道:
- 去重:同一个实例里 sha256 相同的生成文件不会重复落库,避免 agent 反复生成同名文件撑爆存储。
- 单实例上限 500 条:生成文件数到 500 会停扫新的,日志里有 warn。长期跑的实例要定期清 outbox。
- 图片回传给模型:agent 生成的图片、或你上传的图片附件,会以 base64 原生多模态消息的形式塞进下一轮对话,所以"让 agent 看图改图"这种闭环是通的。
这样整条链路就闭环了:你用第一节/第二节把资料传进实例 → agent 在对话里读这些文件(上下文已注入路径)→ agent 跑工具生成新文件落到 outbox → 平台自动回捞 → 你用第三节下载。从传到回传,全在 API 上。
小结
给 AI Agent 喂资料这件事,关键就四个接口:POST .../files 传小文件、upload-config + confirm 走七牛直传大文件、GET .../files 列、GET .../files/:fileId/download 下。限制记住两组数:单文件 20MB、用户总配额 500MB(按套餐可调)。agent 生成的文件不用管,平台自动从 outbox 回捞。
配合之前的几篇:起实例看《如何用 API 一键部署 AI Agent》,发消息看《怡途Claw 流式对话 API 实战》,换模型看《怡途Claw 在线切换大模型供应商实战》,看用量计费看《怡途Claw 计费 API 查余额与用量明细》。文件这条线接上之后,一个能读你资料、能产出文件、能切换模型的数字员工,API 层就齐了。