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 驱动调用,全部请求响应逐行照抄。

PuterPuterDockerCompose文件传输文件服务器2026/11/01

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.localhostconfig.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.localhostGUI 主域浏览器桌面、登录/注册页面流
api.puter.localhostAPI 子域/fs/*、/drivers/call、/whoami 等 REST/驱动接口
site.puter.localhost静态站点托管用户发布的静态网站
app.puter.localhost私有应用托管Dev Center 发布的应用

两个由源码确认、实测验证的路由约束:

  1. /login 与 /signup 是「仅 GUI 域」路由(源码装饰器 guiOriginOnly: true)。用 Host: api.puter.localhost 请求 /login 会得到 404;必须用 Host: puter.localhost。
  2. 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):

域端点方法认证作用
认证/loginPOST否(GUI 域)密码登录换取 JWT
认证/signupPOST否(GUI 域)注册账号
认证/whoamiGET是查询当前身份
文件系统/fs/writePOST是写入/覆盖文件
文件系统/fs/statPOST是查询条目元数据
文件系统/fs/readGET是下载文件内容
文件系统/fs/readdirGET/POST是列目录
文件系统/fs/mkdirPOST是创建目录
文件系统/fs/copyPOST是复制条目
文件系统/fs/movePOST是移动条目
文件系统/fs/renamePOST是重命名
文件系统/fs/deletePOST是删除条目
文件系统/dfGET/POST是存储用量/配额
驱动/drivers/callPOST是KV/AI 等统一驱动调用
驱动/whoamiGET是令牌自检

说明:/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.* 子域)
typefolder / 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。请求三要素:

  1. Content-Type: text/plain;actually=json(2.3 节);
  2. 请求体固定骨架:interface(驱动接口名)、method(方法名)、args(参数对象)、auth_token(可复用 Bearer 头则传 null);
  3. 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. 错误码汇总与处理建议

实测汇总的错误响应一览:

HTTPcode触发场景处理建议
401token_missing未带 Authorization 头登录取 token 后重放
404not_found路由不存在(含 Host 头用错、旧版路由已下线、驱动未就绪)核对 Host 头与端点清单
400bad_requestbody 缺必填字段(如 mkdir 缺 path)、/fs/write 字段名写错按第 5 节参数结构修正
404subject_does_not_existstat/move/delete 操作不存在的条目先 /fs/stat 确认路径
200item_with_same_name_existsmove/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. 参考链接

  1. Puter 官方仓库:https://github.com/HeyPuter/puter(26.10.1,AGPL-3.0)
  2. 官方 API 文档(puter.js SDK):https://docs.puter.com/
  3. 官方自托管文档:https://github.com/HeyPuter/puter/blob/main/doc/self-hosting.md
  4. 本专栏相关篇目:《Puter 介绍与选型指南》、《Puter Docker Compose 部署指南》、《Puter 26.10.1 配置参数详解》
Puter 26.10.1 API 使用详解 - Quzuo