Puter 26.10.1 API 使用详解
Puter 26.10.1 API 实操篇:单机 Docker Compose 实测环境逐域验证认证(/login 的 GUI 域 Host 约束)、文件系统 v2 路由(/fs/write 的 fileContent 字段、/fs/read GET 下载)、统一驱动网关(text/plain;actually=json)与 KV/AI 驱动调用,全部请求响应逐行照抄。
Puter 26.10.1 API 使用详解
目标:本文是 Puter 26.10.1 的 API 实操篇,基于单机 Docker Compose 实测环境(部署过程见 《Puter Docker Compose 部署指南》)逐域调用验证。覆盖认证、文件系统(/fs/* v2 路由)、KV 键值存储(统一驱动网关 /drivers/call)与 AI 驱动四类接口,全部请求/响应回显均来自真实环境逐行照抄,未做任何编造。
1. 环境信息
本文所有实测基于以下环境,读者复现时版本一致则行为应完全一致:
| 项目 | 值 | 说明 |
|---|---|---|
| Puter 版本 | 26.10.1 | 官方 2026-10-25 发布的最新稳定版 |
| 部署形态 | Docker Compose 单机栈 | 7 服务:app / caddy / mariadb / valkey / dynamo / rustfs / s3-init |
| 访问入口 | http://localhost:8140 | 宿主机映射端口,Caddy 反代至 app 容器 4100 |
| 配置域名 | puter.localhost | config.json 中 domain,子域路由基于它展开 |
| 演示账号 | admin / q1w2e3r4 | 自注册管理员账号 |
| 客户端工具 | curl 8.x | 全文示例均为 curl,可直接复制执行 |
本文示例均通过 Caddy 入口(8140 端口)发起,与生产环境通过 80/443 访问的路径一致。
2. 前置条件
调用 API 前需要理解 Puter 的三个访问约定,它们与常见 REST 服务不同,是实测中最容易踩坑的地方。
2.1 子域路由模型:Host 头决定入口行为
Puter 没有独立的 API 服务器——主应用按请求的 Host 头在进程内部分流。以 domain: "puter.localhost" 为例:
| Host 头 | 分流目标 | 典型用途 |
|---|---|---|
puter.localhost | GUI 主域 | 浏览器桌面、登录/注册页面流 |
api.puter.localhost | API 子域 | /fs/*、/drivers/call、/whoami 等 REST/驱动接口 |
site.puter.localhost | 静态站点托管 | 用户发布的静态网站 |
app.puter.localhost | 私有应用托管 | Dev Center 发布的应用 |
两个由源码确认、实测验证的路由约束:
/login与/signup是「仅 GUI 域」路由(源码装饰器guiOriginOnly: true)。用Host: api.puter.localhost请求/login会得到 404;必须用Host: puter.localhost。- API 数据接口(/fs/*、/drivers/call)在 api 子域访问。直接用主域 Host 也能命中部分路由,但行为可能不同(如页面流介入),建议严格区分。
本地测试无法配置 DNS 时,用 curl 的 -H "Host: ..." 覆写即可模拟子域。
2.2 认证方式:Bearer JWT
Puter 的 API 认证是标准的 Authorization: Bearer <JWT>,令牌来自 /login 响应(见第 4 节)。JWT 头部形如 {"alg":"HS256","typ":"JWT","kid":"v2"},由 config.json 的 jwt_secret_v2 签名——这也是为什么官方要求生产环境必须用 openssl rand -hex 64 更换默认密钥。
2.3 统一驱动网关的怪异 Content-Type
KV、AI 等能力走统一驱动网关 POST /drivers/call,它要求一个非常规的 Content-Type:
Content-Type: text/plain;actually=json
请求体仍是 JSON,但服务端按这个特殊头识别驱动调用流量(源码常量 DRIVER_CONTENT_TYPE)。用 application/json 请求 /drivers/call 会得到错误响应。这是实测中第二个高频坑。
3. API 总览
本文实测覆盖的端点清单(版本 26.10.1):
| 域 | 端点 | 方法 | 认证 | 作用 |
|---|---|---|---|---|
| 认证 | /login | POST | 否(GUI 域) | 密码登录换取 JWT |
| 认证 | /signup | POST | 否(GUI 域) | 注册账号 |
| 认证 | /whoami | GET | 是 | 查询当前身份 |
| 文件系统 | /fs/write | POST | 是 | 写入/覆盖文件 |
| 文件系统 | /fs/stat | POST | 是 | 查询条目元数据 |
| 文件系统 | /fs/read | GET | 是 | 下载文件内容 |
| 文件系统 | /fs/readdir | GET/POST | 是 | 列目录 |
| 文件系统 | /fs/mkdir | POST | 是 | 创建目录 |
| 文件系统 | /fs/copy | POST | 是 | 复制条目 |
| 文件系统 | /fs/move | POST | 是 | 移动条目 |
| 文件系统 | /fs/rename | POST | 是 | 重命名 |
| 文件系统 | /fs/delete | POST | 是 | 删除条目 |
| 文件系统 | /df | GET/POST | 是 | 存储用量/配额 |
| 驱动 | /drivers/call | POST | 是 | KV/AI 等统一驱动调用 |
| 驱动 | /whoami | GET | 是 | 令牌自检 |
说明:
/fs/write、/fs/read等是 26.x 的 v2 路由。更早版本文档中的/up、/write、/list等旧路由在 26.10.1 已返回 404({"error":"Not Found","code":"not_found"}),网上旧教程请勿直接照搬。
4. 认证与首次调用
4.1 登录获取令牌
登录请求必须携带 GUI 主域 Host 头(2.1 节约束 1):
curl -s -X POST http://localhost:8140/login \
-H "Host: puter.localhost" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"q1w2e3r4"}'
实测响应(200,token 为 314 字符 JWT,此处节选):
{"proceed":true,"next_step":"complete","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6InYyIn0.eyJ0IjoiZ3VpIiwidiI6IjIiLCJ1IjoiMXNzUjhNWWlTclM0YmxxRVlqbTVuZz09Iiwic3UiOiIxc3NSOE1ZaVNyUzRibHFFWWptNW5nPT0iLCJ1dSI6ImZsakJPS0lJUlRHN0NVNi9MTHZ3a3c9PSIsImFpIjoiZmxqQk9LSUlSVEc3Q1U2L0xMdndrdz09IiwiaWF0IjoxNzkzNTAzMTEzfQ.2FOa02jaWJh2frE6OIhX3AuiLH7my5TO5aowtp1J2jc","user":{"username":"admin","uuid":"7e58c138-a208-4531-bb09-4ebf2cbbf093","email":null,"unconfirmed_email":null,"email_confirmed":true, …}}
字段说明:
proceed: true且next_step: "complete":一步登录完成;若启用 2FA 会返回next_step: "otp"等中间态。token:后续所有 API 调用的 Bearer 令牌。user:当前用户信息,uuid是全局唯一标识(fsEntry 的uid与之关联)。
如果 Host 头用错(Host: api.puter.localhost),同一请求返回:
{"error":"Not Found","message":"Not Found","code":"not_found"}
4.2 无认证调用的报错形态
不带 Authorization 访问受保护接口(以 /whoami 为例):
curl -s http://localhost:8140/whoami -H "Host: api.puter.localhost"
实测响应(401):
{"error":"Missing authentication token","code":"token_missing"}
4.3 令牌自检
TOKEN=<登录返回的 token>
curl -s http://localhost:8140/whoami \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN"
实测响应(200,节选):
{"username":"admin","uuid":"7e58c138-a208-4531-bb09-4ebf2cbbf093","email":null,"is_temp":false,"human_name":"admin", …}
后文示例统一用环境变量
TOKEN承载令牌;H1指Host: api.puter.localhost。token 全长 314 字符,示例中展示为节选。
5. 文件系统 API(/fs/*)
文件系统是 Puter 最核心的 API 域。所有条目(文件/目录)统一用 fsEntry 结构表示,实测完整字段如下(/fs/readdir 返回的单条目录项,逐行照抄):
{"uuid":"765d38b8-88de-44c6-bd52-ecb3ee28db2b","uid":"765d38b8-88de-44c6-bd52-ecb3ee28db2b","parentUid":"538c7e84-1b54-4ae7-b861-ec301114bbe6","path":"/admin/AppData","name":"AppData","isDir":true,"isShortcut":false,"shortcutTo":null,"isSymlink":false,"symlinkPath":null,"isPublic":null,"immutable":true,"metadata":null,"modified":1793501030,"created":1793501030,"accessed":null,"size":null,"layout":null,"subdomains":[],"workers":[],"hasWebsite":false,"suggestedApps":[],"isShared":false,"type":"folder","thumbnail":null,"associatedApp":null}
字段速览:
| 字段 | 含义 |
|---|---|
uuid / uid | 条目全局唯一 ID(两者取值相同) |
parentUid | 父目录 uid |
path | 绝对路径,根为 /,用户主目录为 /<username> |
isDir | 是否目录 |
immutable | 保留目录(如 AppData)不可改写 |
size | 文件字节数(目录为 null) |
hasWebsite | 是否已发布为静态站点(配合 site.* 子域) |
type | folder / file 等类型串 |
5.1 列目录:/fs/readdir
curl -s -X POST http://localhost:8140/fs/readdir \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"path":"/admin"}'
实测响应(200,新用户主目录含 7 个标准目录):AppData、Desktop、Documents、Downloads、Music、Pictures、Public(数组,每项为完整 fsEntry)。
也支持 GET 形式:GET /fs/readdir?path=/admin,行为一致。
5.2 写入文件:/fs/write
/fs/write 接收 JSON 对象,内容字段名是 fileContent,元数据字段名是 fileMetadata(不是直觉的 content/path 平铺):
curl -s -X POST http://localhost:8140/fs/write \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"fileMetadata":{"path":"/admin/hello-d5.txt","size":24},"fileContent":"hello from curl D5 test"}'
实测响应(200):
{"fsEntry":{"uuid":"53950d53-43ee-4693-88d3-267bc3cd4669","uid":"53950d53-43ee-4693-88d3-267bc3cd4669","parentUid":"538c7e84-1b54-4ae7-b861-ec301114bbe6","path":"/admin/hello-d5.txt","name":"hello-d5.txt","isDir":false,"isShortcut":false,"shortcutTo":null,"isSymlink":false,"symlinkPath":null,"isPublic":null,"immutable":false,"metadata":"{\"contentType\":\"application/octet-stream\"}","modified":1793503127,"created":1793503127,"accessed":1793503127,"size":23,"layout":null,"subdomains":[],"workers":[],"hasWebsite":false,"suggestedApps":[]},"wasOverwrite":false,"requestedThumbnail":null,"contentHashSha256":"c2ae125b496ed76abe067be3a00abde89195a456b3bf6790b59820a3b9243be6"}
实测要点:
fileContent为字符串时直接按文本写入;二进制内容需配合encoding字段(源码支持 Uint8Array/Buffer/流等,curl 场景用 base64 + encoding 即可)。fileMetadata.size服务端以实际内容长度为准——示例声明 24 字节但内容 23 字节,响应size: 23。- 响应附
contentHashSha256,可用于客户端校验完整性。 - 再次写入同路径时
wasOverwrite: true。 - 常见错误:把 body 写成
{"path":"/…","content":"…"}平铺形式会得到 400{"error":"Invalid file size","code":"bad_request"}(服务端把 content 当元数据解析,size 缺失)。
5.3 查询元数据:/fs/stat
curl -s -X POST http://localhost:8140/fs/stat \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"path":"/admin/hello-d5.txt"}'
实测响应(200):完整 fsEntry,与 5.1 条目结构一致,另多一个 "isShared":false 字段。
路径不存在时的实测响应(404):
{"error":"Subject does not exist","message":"Subject does not exist","code":"subject_does_not_exist"}
5.4 读取内容:GET /fs/read
读文件是 GET 路由,路径走 query 参数(不是 POST body):
curl -s "http://localhost:8140/fs/read?path=/admin/hello-d5.txt" \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN" -D -
实测响应头(节选)与响应体:
HTTP/1.1 200 OK
Content-Disposition: inline; filename="hello-d5.txt"
Content-Length: 23
Content-Type: application/octet-stream
Etag: "ff92675dc0c7f2b008bcc04b5619fcdd"
Last-Modified: Sun, 01 Nov 2026 03:18:47 GMT
hello from curl D5 test
内容与 5.2 写入完全一致,闭环验证通过。
5.5 创建目录:/fs/mkdir
curl -s -X POST http://localhost:8140/fs/mkdir \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"path":"/admin/d5-test-dir"}'
实测响应(200,节选):
{"uuid":"98ce114d-698e-4435-b76a-febabd2f7476","uid":"98ce114d-698e-4435-b76a-febabd2f7476","parentUid":"538c7e84-1b54-4ae7-b861-ec301114bbe6","path":"/admin/d5-test-dir","name":"d5-test-dir","isDir":true, …}
支持的完整参数(源码确认):path(必填,完整目标路径)、overwrite、dedupe_name(同名时自动加后缀)、create_missing_parents(自动补齐缺失的父目录)。缺 path 时返回 400:
{"error":"Missing `path`","message":"Missing `path`","code":"bad_request"}
5.6 复制:/fs/copy
source 是被复制条目,destination 是目标父目录(不是目标全路径):
curl -s -X POST http://localhost:8140/fs/copy \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"source":"/admin/hello-d5.txt","destination":"/admin/d5-test-dir"}'
实测响应(200,节选)——注意生成了新 uuid,路径为 /admin/d5-test-dir/hello-d5.txt:
{"uuid":"ca93c8c2-a5f0-41a2-a5d4-5134f5953505","uid":"ca93c8c2-a5f0-41a2-a5d4-5134f5953505","path":"/admin/d5-test-dir/hello-d5.txt","name":"hello-d5.txt","isDir":false, …}
引用条目除 path 外还可用 uid 或 id(源码 #extractNodeRef),缺失时返回 400:
{"error":"Missing entry reference (expected one of: path, uid, id)","message":"Missing entry reference (expected one of: path, uid, id)","code":"bad_request"}
其他参数:new_name(复制后重命名)、overwrite(覆盖同名目标)、dedupe_name。
5.7 移动:/fs/move
参数结构与 /fs/copy 相同(source + destination 父目录):
curl -s -X POST http://localhost:8140/fs/move \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"source":"/admin/move-me.txt","destination":"/admin/d5-renamed-dir"}'
实测响应(200,节选)——fsEntry 的 path 已更新为新位置:
{"uuid":"e676eab1-9323-4077-971a-5c00fd1a7bf3","uid":"e676eab1-9323-4077-971a-5c00fd1a7bf3","parentUid":"98ce114d-698e-4435-b76a-febabd2f7476","path":"/admin/d5-renamed-dir/move-me.txt","name":"move-me.txt","isDir":false, …}
两个实测错误形态:
- 目标目录已有同名条目(200 状态,业务冲突):
{"error":"An entry named `hello-d5.txt` already exists","message":"An entry named `hello-d5.txt` already exists","code":"item_with_same_name_exists","entry_name":"hello-d5.txt"}
- 源条目不存在(404):
{"error":"Subject does not exist","code":"subject_does_not_exist"}
5.8 重命名:/fs/rename
curl -s -X POST http://localhost:8140/fs/rename \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"path":"/admin/d5-test-dir","new_name":"d5-renamed-dir"}'
实测响应(200):完整 fsEntry,path 变为 /admin/d5-renamed-dir,uuid 保持不变(重命名不换 ID)。
5.9 删除:/fs/delete
curl -s -X POST http://localhost:8140/fs/delete \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"path":"/admin/d5-renamed-dir/move-me.txt"}'
实测响应(200):
{"ok":true}
删除后用 /fs/readdir 复查,目标目录中该条目已消失。对不存在路径执行删除同样返回 404 subject_does_not_exist。
5.10 存储配额:/df
配额端点挂在根路径(不在 /fs 前缀下,26.10.1 实测):
curl -s -X POST http://localhost:8140/df \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -d '{}'
实测响应(200):
{"used":46,"capacity":211406221358}
used:当前用户已用字节数。capacity:可用容量上限。该环境is_storage_limited: false(不限额配置),故 capacity 反映的是宿主机磁盘可用量(约 211 GB);开启限额后为 config.json 中storage_capacity的值。
注意:请求 POST /fs/df 会得到 404 not_found——这是 v2 路由与遗留路由混排的又一实证。
6. KV 存储 API(统一驱动网关)
Puter 把 KV、AI 等能力统一收敛到驱动网关 POST /drivers/call。请求三要素:
Content-Type: text/plain;actually=json(2.3 节);- 请求体固定骨架:
interface(驱动接口名)、method(方法名)、args(参数对象)、auth_token(可复用 Bearer 头则传 null); Authorization: Bearer $TOKEN认证头。
6.1 写入:method=set
curl -s -X POST http://localhost:8140/drivers/call \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: text/plain;actually=json' \
-d '{"interface":"puter-kvstore","driver":null,"test_mode":false,"method":"set","args":{"key":"demo:greeting","value":"hello from curl"},"auth_token":null}'
实测响应(200):
{"success":true,"result":true,"service":{"name":"puter-kvstore"}}
6.2 读取:method=get
curl -s -X POST http://localhost:8140/drivers/call \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: text/plain;actually=json' \
-d '{"interface":"puter-kvstore","driver":null,"test_mode":false,"method":"get","args":{"key":"demo:greeting"},"auth_token":null}'
实测响应(200):
{"success":true,"result":"hello from curl","service":{"name":"puter-kvstore"}}
6.3 列举:method=list
curl -s -X POST http://localhost:8140/drivers/call \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: text/plain;actually=json' \
-d '{"interface":"puter-kvstore","driver":null,"test_mode":false,"method":"list","args":{},"auth_token":null}'
实测响应(200):
{"success":true,"result":[{"key":"demo:greeting","value":"hello from curl"}],"service":{"name":"puter-kvstore"}}
KV 数据按用户隔离(底层为 DynamoDB,栈内由 dynamodb-local 提供)。puter-kvstore 接口还有 del、incr、decr 等方法,参数模式与 set/get 一致。
6.4 驱动接口错误形态
不存在的接口名(实测 interface: "no-such-iface",404):
{"error":"Driver not found: no-such-iface:(no default)","message":"Driver not found: no-such-iface:(no default)","code":"not_found"}
Driver not found: <接口名>:(no default) 的含义是:该接口存在但没有可用的默认驱动实现。这个语义在 AI 驱动一节会再次遇到。
7. AI 驱动 API
AI 能力同样走 /drivers/call,接口名为 puter-ai(puter.ai.chat() SDK 方法对应它)。在未配置任何 AI 提供商的干净自托管环境(config.json 未配 OpenAI/Anthropic 密钥、未启用 Ollama)实测:
curl -s -X POST http://localhost:8140/drivers/call \
-H "Host: api.puter.localhost" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: text/plain;actually=json' \
-d '{"interface":"puter-ai","driver":null,"test_mode":false,"method":"chat","args":{"messages":[{"role":"user","content":"hi"}]},"auth_token":null}'
实测响应(404):
{"error":"Driver not found: puter-ai:(no default)","message":"Driver not found: puter-ai:(no default)","code":"not_found"}
结论与建议:
- 自托管环境要让
puter-ai可用,需二选一:在 config.json 配置providers.ollama.enabled: true并部署本地 Ollama,或通过计量/订阅配置接入外部模型密钥(相关参数见 《Puter 26.10.1 配置参数详解》)。 - 另注意:AI 相关端点属于声明为付费计划才开放的表面,自托管默认免费策略下即便驱动就绪,也可能遇到 402
subscription_required;官方自托管文档建议追加"meteringEnforcement": {"subscriptions": false}关闭该门槛。
8. 错误码汇总与处理建议
实测汇总的错误响应一览:
| HTTP | code | 触发场景 | 处理建议 |
|---|---|---|---|
| 401 | token_missing | 未带 Authorization 头 | 登录取 token 后重放 |
| 404 | not_found | 路由不存在(含 Host 头用错、旧版路由已下线、驱动未就绪) | 核对 Host 头与端点清单 |
| 400 | bad_request | body 缺必填字段(如 mkdir 缺 path)、/fs/write 字段名写错 | 按第 5 节参数结构修正 |
| 404 | subject_does_not_exist | stat/move/delete 操作不存在的条目 | 先 /fs/stat 确认路径 |
| 200 | item_with_same_name_exists | move/copy 目标有同名条目 | 加 overwrite: true 或 new_name |
通用排查顺序:Host 头 → 认证头 → Content-Type → 路径与字段名。前两者错了通常连端点都进不去(404/401),后两者错了才是业务级 400。
9. 常见问题
Q1:为什么 /login 返回 404?
/login、/signup 是仅 GUI 域路由(源码 guiOriginOnly: true),必须用 Host: puter.localhost(主域)访问;Host: api.puter.localhost 会得到 {"error":"Not Found","code":"not_found"}。这是本文实测中唯一一个「换对 Host 头立即从 404 变 200」的端点。
Q2:/fs/write 提示 Invalid file size?
请求体结构写错了。26.10.1 的 /fs/write 要求 {"fileMetadata":{"path":"…","size":…},"fileContent":"…"}——内容放 fileContent,路径与大小放 fileMetadata。平铺的 {"path":"…","content":"…"} 会被当元数据解析而失败。字段名以服务端源码为准(FSService 的 write 方法读取 writeRequest.fileContent)。
Q3:/drivers/call 返回请求体错误?
Content-Type 必须是 text/plain;actually=json。这个怪异值是 Puter 驱动网关的协议约定(源码常量 DRIVER_CONTENT_TYPE),用 application/json 不行。
Q4:网上教程里的 /up、/write、/list 全部 404?
那些是旧版路由。26.10.1 已全面切换 v2 路由:写入用 POST /fs/write,下载用 GET /fs/read,列目录用 POST /fs/readdir。旧教程(含部分官方 Wiki 页面)的端点清单已过时,本文第 3 节清单可直接对照使用。
Q5:/fs/df 为什么 404?
配额端点在根路径 POST /df(遗留路由位置),不在 /fs 前缀下。26.10.1 实测 /df 返回 200,/fs/df 返回 404。
Q6:KV 写入后另一个用户能读到吗?
不能。KV 数据按用户隔离,args 中无需传用户标识——服务端从 auth_token 解析所属用户。换一个用户的 token 执行 get 会得到 result: null。
10. 参考链接
- Puter 官方仓库:https://github.com/HeyPuter/puter(26.10.1,AGPL-3.0)
- 官方 API 文档(puter.js SDK):https://docs.puter.com/
- 官方自托管文档:https://github.com/HeyPuter/puter/blob/main/doc/self-hosting.md
- 本专栏相关篇目:《Puter 介绍与选型指南》、《Puter Docker Compose 部署指南》、《Puter 26.10.1 配置参数详解》