{"ok":true,"service":"foragent-share","description":"R2 文件上传 / 下载 / 分享 API（纯 API，无网页界面）","version":"0.2.0","auth":{"scheme":"Bearer","header":"Authorization: Bearer <token>","alternatives":["X-Auth-Token: <token>","?token=<token>（会进服务器日志，非必要不用）"],"you":{"role":"anon","permissions":[]},"noToken":{"meaning":"你现在是匿名身份，没有任何权限，无法上传/下载/列目录。","ifYouHaveAShareLink":"如果你手里有一条 /s/... 链接，它本身就是入场券 —— 不需要 token，直接用（见 ifYouHaveAShareLink）。","ifYouNeedFullAccess":"如果你需要完整权限，向这个服务的 owner 索取 token，然后用 Authorization: Bearer <token> 请求头调用。","ifYouAreTheOwner":"如果你就是 owner：用你部署时保存下来的那份 token（secret 是只写的，写进去就读不回来了，所以本地那份副本是唯一的）。"}},"quickstart":{"note":"三步串起来。每步的 take 是「这个响应里哪些字段下一步要用」。","steps":[{"step":1,"goal":"上传","request":"curl -X POST \"https://share.foragent.cc/v1/files?filename=notes.md\" -H \"authorization: Bearer $TOKEN\" -H \"content-type: text/markdown\" --data-binary @notes.md","response":{"ok":true,"file":{"id":"0192f3a1-8b4c-7d2e-9f01-3a5b6c7d8e9f","filename":"notes.md","contentType":"text/markdown; charset=utf-8","size":1234,"uploadedAt":"2026-09-13T01:57:22.567Z","etag":"728a23775de8d9d815a184dc41d0ef19","urls":{"self":"https://share.foragent.cc/v1/files/<id>","download":"https://share.foragent.cc/v1/files/<id>/content"}},"uploadedBy":{"role":"admin","permissions":["download","list","share","upload"]}},"take":"file.id 就是下面所有请求里的 <id>。它形如 UUIDv7。","note":"原始 body 直传是流式写入，不占内存，优先用这种。文件名放 ?filename=。"},{"step":2,"goal":"下载","request":"curl -H \"authorization: Bearer $TOKEN\" \"https://share.foragent.cc/v1/files/<file.id>/content\" -o out.bin","response":"（原始文件字节，不是 JSON）","take":"加 ?dl=1 变强制下载；带 Range: bytes=0-499 取片段；带 If-None-Match: <etag> 可拿 304。"},{"step":3,"goal":"分享（给别人看，对方不需要 token）","request":"curl -X POST \"https://share.foragent.cc/v1/files/<file.id>/share\" -H \"authorization: Bearer $TOKEN\" -H \"content-type: application/json\" -d '{\"ttl\":86400}'","response":{"ok":true,"shareId":"0192f3a1-8b4c-7d2e-9f01-3a5b6c7d8e9f","url":"https://share.foragent.cc/s/0192f3a1-8b4c-7d2e-9f01-3a5b6c7d8e9f","apiUrl":"https://share.foragent.cc/v1/shares/0192f3a1-8b4c-7d2e-9f01-3a5b6c7d8e9f","expiresAt":"2026-09-20T01:57:35.000Z","expiresIn":86400,"maxDownloads":null,"revocable":true},"take":"url 就是发给别人的链接。shareId 用于「吊销」：DELETE /v1/shares/<shareId>。","note":"body 可选字段：ttl（秒，会被夹在 60 与 MAX_SHARE_TTL 之间）、maxDownloads（次数上限，需要 KV）、download（true = 强制附件下载）。"}]},"ifYouHaveAShareLink":{"whatItIs":"形如 /s/<shareId> 的链接就是一条无需 token 的凭证，直接 GET 即可拿到文件字节。","doThis":"curl -L \"https://share.foragent.cc/s/<shareId>\" -o file.bin","youCan":["查看 / 下载这一个文件"],"youCannot":["列目录","上传","删除","看到其他文件"],"variants":{"forceDownload":"加 ?dl=1 → Content-Disposition: attachment","apiForm":"把 /s/ 换成 /v1/shares/ 走 API 路径，返回同样的字节"},"ifItFails":{"404 share_not_found":"链接不存在或已过期（KV 记录到期）","410 share_expired":"链接过期了，向分享者要一条新的","410 share_revoked":"分享者主动吊销了","410 share_exhausted":"下载次数用完了"}},"endpoints":[{"method":"GET","path":"/","permission":"none","desc":"本清单（agent 入口文档）"},{"method":"GET","path":"/health","permission":"none","desc":"存活探测"},{"method":"GET","path":"/v1/me","permission":"none","desc":"自检：我是什么身份、有什么权限"},{"method":"POST","path":"/v1/files?filename=NAME","permission":"upload","desc":"上传原始 body（推荐，流式）"},{"method":"POST","path":"/v1/files","permission":"upload","desc":"multipart/form-data 上传（字段 file）"},{"method":"GET","path":"/v1/files?limit=&cursor=&q=&order=asc|desc","permission":"list","desc":"列表"},{"method":"GET","path":"/v1/files/:id","permission":"download","desc":"文件详情（JSON）"},{"method":"GET","path":"/v1/files/:id/meta","permission":"download","desc":"只要元数据（不读内容）"},{"method":"GET","path":"/v1/files/:id/content","permission":"download","desc":"下载（Range / HEAD / 304 / ?dl=1）"},{"method":"POST","path":"/v1/files/:id/share","permission":"share","desc":"签发分享链接"},{"method":"DELETE","path":"/v1/files/:id","permission":"upload","desc":"删除文件"},{"method":"POST","path":"/v1/shares","permission":"share","desc":"按 {key|id} 签发分享链接"},{"method":"GET","path":"/v1/shares?limit=","permission":"share","desc":"列出我签发过的分享（需 KV）"},{"method":"DELETE","path":"/v1/shares/:shareId","permission":"share","desc":"吊销分享链接（需 KV）"},{"method":"GET","path":"/s/:shareId","permission":"none","desc":"分享短链（UUIDv7，谁都能开）"},{"method":"GET","path":"/s/:key?exp=&sig=","permission":"none","desc":"无状态签名短链（未配 KV 时的回退形态）"}],"errors":{"shape":{"error":"人类可读说明","code":"机器可读的 snake_case 码"},"codes":{"unauthorized":"401 缺少或无效凭证 → 加 Authorization 头，或找 owner 要 token","forbidden":"403 身份有效但权限不够 → 换有对应权限的 token","not_found":"404 对象不存在","share_not_found":"404 分享链接不存在或已过期","share_expired":"410 分享链接过期 → 向分享者要新的","share_revoked":"410 分享链接被主动吊销 → 放弃","share_exhausted":"410 下载次数用尽 → 向分享者要新的","share_invalid":"403 分享链接签名无效（被篡改，或旧形态链接带了不该带的参数）→ 放弃","kv_required":"501 该功能需要服务端配置 SHARE_KV → 找 owner","metadata_too_large":"400 自定义元数据超过 2KiB → 减少 x-meta-* 头","payload_too_large":"400 文件超过 MAX_UPLOAD_SIZE → 改小或找 owner 调上限","bad_request":"400 参数不对，看 error 字段的说明","internal_error":"500 服务端问题 → 可以重试，持续失败就找 owner"}},"notes":["所有响应都是 JSON，唯一例外是下载接口（返回文件字节）。","列表 / 详情里的 urls 字段已经带好了可直接调的绝对地址，不用自己拼。","上传支持 x-meta-<key>: <value> 自定义元数据（最多 16 键 / 合计 2KiB），会在列表和详情里回显。","上传支持 x-content-sha256: <hex> 记录校验和，之后能从 sha256 字段读回。","下载的 Range 只支持单段 bytes=；多段或非法 Range 会忽略并按 200 返回整份。","吊销（DELETE /v1/shares/:shareId）不是立刻全局生效：底层 KV 最终一致，读缓存最长 60 秒，期间被吊销的链接仍可能返回 200。你吊销后立刻验证会看到这个假象 —— 重试几次或等一会儿再看。需要立刻彻底作废请轮换 SHARE_SECRET（会让所有链接失效）。"]}