Scoold 1.70 REST API 使用详解
Scoold 双服务接口体系实测详解:Scoold 业务 API(/api,JWT Token 认证)与 Para 数据 API(/v1,SigV4 签名)双轨拆解,覆盖问答 CRUD、投票、采纳、评论、标签、用户、搜索全流程,含 PATCH 缺 tags 500、自投票 400、SigV4 漏签 query 403 等实测排障实录。
Scoold 1.70 REST API 使用详解
目标:掌握 Scoold 双服务接口体系的完整调用方法——Scoold 业务 API(
/api,JWT Token 认证)与 Para 数据 API(/v1,SigV4 签名认证),覆盖问答 CRUD、投票、采纳、评论、标签、用户、搜索全流程。文中全部请求与响应均已在真实 Linux 服务器(Scoold 1.70.2 + Para 1.57.0)上用 curl 实测通过,回显逐行照抄。
一、环境信息
- 操作系统:Linux x86_64(root 账号操作);
- Scoold 1.70.2(Web 服务,监听 8131 端口,REST API 与 Web 页面同端口);Para 1.57.0(数据服务,监听 8132 端口);
- 部署形态与数据目录按《Scoold 1.70 Docker Compose 部署指南》完成,两份
application.conf已挂载就绪; - 管理员账号:
admin@scoold.local/q1w2e3r4(测试用户bob@scoold.local/q1w2e3r4由 API 创建); - 客户端工具:
curl(HTTP 调用)、python3(构造 Para 签名)。
💡 说明:Scoold 的 REST API 默认关闭。开箱状态调用任何
/api端点都会得到 403,需要先在配置中开启,见下文 2.1。
二、前置条件
2.1 开启 Scoold REST API
Scoold 的 API 由两个配置项控制(参数语义详见《Scoold 1.70 配置参数详解》):
# scoold-application.conf
scoold.api_enabled = true # 总开关,默认 false
scoold.api_user_access_enabled = true # 允许普通用户(非管理员)也通过 API 访问
api_enabled 属于启动期参数,修改后需重启 Scoold 进程生效。本文实测环境两值均已置 true。
2.2 生成个人 API Token
Scoold API 的认证凭据是个人 API Token(JWT),与登录用户绑定,权限等同该用户在页面上的操作权限。生成入口在 Web 界面右上角头像 → Settings → API keys,点击生成后界面直接显示完整 Token。实测生成的 Token(JWT)有效期默认 7 天:
{"jwt":"eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIyMDI2OTcwNzQ4ODE1NDEzMjQ4IiwibmJmIjoxNzkzMzUzMTEwLCJhcHBpZCI6ImFwcDpzY29vbGQiLCJncm91cHMiOiJhZG1pbnMiLCJleHAiOjE3OTM5NTc5MTAsImlhdCI6MTc5MzM1MzExMCwianRpIjoiMjAyNjk3MDc0ODgxNTQxMzI0OCJ9.<签名段>","exp":"2026-11-06 09:38","jti":"2026970748815413248"}
JWT 载荷中 groups: "admins" 表示这是管理员令牌;exp(过期时间)由 scoold.personal_token_expires_after(默认 604800 秒)决定,用户在界面上可选 1~2200 小时。同一用户同一时刻只能持有一个有效 Token,重新生成前需先在设置页撤销旧 Token。
后续示例统一用环境变量携带:
export TOKEN='<在 Settings → API keys 页面生成的完整 JWT>'
2.3 准备 Para 接入密钥
Para /v1 数据 API 需要应用密钥对签名。首次部署时 Scoold 通过 autoinit 自动生成 para.para_access_key / para_secret_key 并回写到挂载的配置文件(见部署篇 2.4 节),实测值形如:
para.root_access_key = "app:para"
para.root_secret_key = "IGK7TQ4dVEWH9JMcXWdtDhWS9bEyz9gL7EKMuW5BIefB0l4Zu7JOtg==" # 属系统级密钥,生产环境注意保密
⚠️ 注意:
root密钥可操作 Para 全部管理 API,作用域大于 Scoold 用的app:scoold密钥。本文实测统一使用app:scoold对的密钥(即scoold.para_secret_key,同样 autoinit 回写在 Scoold 侧配置文件中),权限收敛在单个应用内。生产环境请将密钥置于受限权限的配置文件中,禁止提交到代码仓库。
2.4 在线接口文档
Scoold 内置 Swagger UI,浏览器访问 http://<服务器>:8131/apidocs(需登录后交互调试)。未登录直接访问只能看到文档页框架,无法加载接口定义。
三、API 总览
3.1 双服务接口体系
Scoold 对外暴露两层 HTTP 接口,职责与认证方式完全不同:
| 层 | 基地址 | 职责 | 认证方式 | 典型调用方 |
|---|---|---|---|---|
| Scoold 业务 API | http://<服务器>:8131/api | 问答/投票/评论/标签/用户/搜索等业务封装,内置声望、限速、空间权限校验 | Authorization: Bearer <个人 API Token> | 脚本、机器人、第三方集成 |
| Para 数据 API | http://<服务器>:8132/v1 | 对象存储与检索层,直接读写底层对象(question/reply/profile/tag…) | AWS SigV4 请求签名 | Scoold 服务自身、数据迁移/备份脚本 |
Scoold 页面上的每一次点击(提问、投票、评论)最终都会转化为一次对 Para 的签名调用。业务 API 是「带规则的门」,数据 API 是「直通仓库的门」——集成开发优先走前者,只有备份、迁移、批量分析等场景才直接调 Para。
3.2 Scoold /api 端点清单(实测归纳)
| 方法与路径 | 用途 | 实测结果 |
|---|---|---|
GET /api | API 自检与文档入口 | 200 |
GET /api/ping | 存活探测 | 200 pong |
GET /api/stats | 站点统计(含版本号) | 200 |
POST /api/posts | 创建问题或回答 | 201 |
GET /api/posts | 问题列表(支持排序/过滤) | 200 |
GET /api/posts/{id} | 问题详情(含回答、评论、相似问题) | 200 |
PATCH /api/posts/{id} | 更新问题/回答 | 200 / 500(缺 tags 时) |
DELETE /api/posts/{id} | 删除问题/回答 | 200 |
PATCH /api/posts/{id}/tags | 增删问题标签 | 200 |
PUT /api/posts/{id}/accept/{replyid} | 采纳回答 | 200 |
PUT /api/posts/{id}/close | 关闭问题 | 200 |
PUT /api/posts/{id}/approve | 版主审核通过 | 200 |
PUT /api/posts/{id}/voteup / votedown | 赞同/反对投票 | 200 / 400(自投) |
GET /api/posts/{id}/answers | 回答列表 | 200 |
GET /api/posts/{id}/comments | 评论列表 | 200 |
GET /api/posts/{id}/revisions | 编辑修订历史 | 200 |
POST /api/comments | 创建评论 | 201 |
GET /api/comments/{id} / DELETE /api/comments/{id} | 读/删评论 | 200 |
POST /api/users / GET /api/users | 创建/查询用户 | 201 / 200 |
PATCH /api/users/{id} | 更新用户资料 | 200 |
PUT /api/users/{id}/ban / moderator | 封禁/授版主 | 200 |
GET /api/tags / POST /api/tags | 标签列表/新建 | 200 / 201 |
GET /api/search/{type}/{query} | 全文搜索 | 200 |
GET /api/spaces | 空间列表 | 200 |
GET /api/events / GET /api/types | 事件类型/对象类型 | 200 |
GET /api/backup / PUT /api/restore | 备份导出/恢复 | 200 |
GET /api/config / PUT /api/config | 读取/修改运行配置 | 200 |
Para /v1 遵循同一套通用对象语法(详见第六章)。
3.3 限流约定
业务 API 继承页面发帖限速:默认每分钟 2 次、每小时 30 次、每天 50 次提问(回答为 2/50/100),由 scoold.rate_limited_posting_enabled 控制。超限调用与页面操作一样被拒绝。Para 数据 API 不做业务限速,但 Scoold 侧对 Para 的调用带有内置的客户端缓存,反复全量拉取时注意网络开销。
四、认证与首次调用(实测)
4.1 Scoold API 自检
curl -sS -H "Authorization: Bearer $TOKEN" http://localhost:8131/api
实测回显:
{"healthy":true,"message":"Scoold API, see docs at http://localhost:8131/apidocs","pro":false}
pro:false 表示当前为开源版(Scoold Pro 的 SAML/SCIM 等特性不可用)。
4.2 存活探测与站点统计
curl -sS -H "Authorization: Bearer $TOKEN" http://localhost:8131/api/ping
实测回显:
pong
curl -sS -H "Authorization: Bearer $TOKEN" http://localhost:8131/api/stats
实测回显:
{"questions":2,"replies":1,"spaces":1,"users":2,"tags":5,"reports":0,"comments":1,"revisions":0,"unapproved_questions":0,"unapproved_replies":0,"para_version":"1.57.0","scoold_version":"1.70.2"}
这条端点同时给出了两个服务的精确版本号,可作为部署验收时的版本取证手段。
4.3 未认证调用的错误对照
curl -sS http://localhost:8131/api/posts
实测回显:
{"error":"Unauthenticated request! 401 Unauthorized"}
HTTP 状态码 401。Bearer Token 错误或过期同样是 401,处理方式见第八章 FAQ。
4.4 Para 健康检查(免签名)
Para 有三个免签名端点:/v1/_health、/v1/_setup、/v1/_config/options。
curl -sS http://localhost:8132/v1/_health
实测回显:
{"message":"healthy"}
/v1/_config/options 返回 Para 全部配置参数的默认值与说明(参数清单见《Scoold 1.70 配置参数详解》第七章),适合脚本比对当前值与默认值。
五、Scoold 业务 API 实测(/api)
5.1 创建问题
curl -sS -w '\nHTTP=%{http_code}\n' -X POST \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"title":"How to disable public user registration via the REST API?","body":"We want to restrict signups to invited users only. Which configuration keys and API calls are involved?","tags":["api","security"]}' \
http://localhost:8131/api/posts
实测回显(节选,HTTP 状态码 201):
{"body":"We want to restrict signups to invited users only. Which configuration keys and API calls are involved?","title":"How to disable public user registration via the REST API?","tags":["api","security"],"viewcount":0,"answerid":null,"answercount":0,"space":"scooldspace:default","id":"2026999042638090240","timestamp":1793358816278,"type":"question","appid":"scoold","creatorid":"2026970748815413248:profile","votes":0,"version":0,"stored":true,"indexed":true,"author":{"name":"admin","groups":"admins","id":"2026970748815413248:profile","votes":13}}
请求体三个核心字段:title(问题标题)、body(正文)、tags(标签数组)。type 缺省为 question;显式传 "type":"reply" 并带 parentid 则创建回答(见 5.3)。响应返回完整对象,其中 id 是后续所有操作的定位符。
5.2 查询问题详情与列表
curl -sS -H "Authorization: Bearer $TOKEN" http://localhost:8131/api/posts/2026999042638090240
实测回显(节选):
{"body":"Restrict signups to invited users only via approved_domains_for_signups or SSO. Updated via the REST API.","title":"How to disable public user registration in Scoold 1.70?","tags":["api","configuration","sso"],"viewcount":0,"answerid":"2026999176172146688","answercount":2,"space":"scooldspace:default","commentIds":["2026999548760559616"],"id":"2026999042638090240","type":"question","votes":1,"author":{"name":"admin","groups":"admins"}}
详情响应比列表多出四块:children(回答数组)、comments(评论数组)、similar(相似问题)、author(作者资料,含头像/声望/徽章等完整档案)。列表接口:
curl -sS -H "Authorization: Bearer $TOKEN" \
'http://localhost:8131/api/posts?sortby=newest&includeReplies=false'
sortby 可选 newest / votes / active / views,filter 参数支持空间过滤;includeReplies=true 时每个问题内嵌回答数组。
5.3 创建回答
curl -sS -w '\nHTTP=%{http_code}\n' -X POST \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"type":"reply","parentid":"2026999042638090240","body":"Set approved_domains_for_signups to your company domain, or front the site with an identity provider (LDAP/OAuth2) so local password signups are disabled. Both are configurable at runtime."}' \
http://localhost:8131/api/posts
实测回显(节选,HTTP 201):
{"body":"Set approved_domains_for_signups to your company domain, or front the site with an identity provider (LDAP/OAuth2) so local password signups are disabled. Both are configurable at runtime.","title":"How to disable public user registration via the REST API?","tags":[],"space":"scooldspace:default","id":"2026999176172146688","type":"reply","parentid":"2026999042638090240","votes":0,"author":{"name":"admin","badges":"noob,eureka,eureka"}}
注意两点:回答自动继承父问题标题(title 回显为父问题标题);回答成功后作者会得到 eureka 徽章。
5.4 更新问题(PATCH)
curl -sS -w '\nHTTP=%{http_code}\n' -X PATCH \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"title":"How to disable public user registration in Scoold 1.70?","body":"Restrict signups to invited users only via approved_domains_for_signups or SSO. Updated via the REST API.","tags":["api","security","configuration"]}' \
http://localhost:8131/api/posts/2026999042638090240
实测回显(HTTP 200):
{"id":"2026999042638090240","title":"How to disable public user registration in Scoold 1.70?","body":"Restrict signups to invited users only via approved_domains_for_signups or SSO. Updated via the REST API.","tags":["api","configuration","security"]}
⚠️ 注意(实测排障):PATCH 请求体必须携带
tags数组,否则服务端拼装标签列表时抛空指针,返回500与{"message":null,"code":500}——这是实测踩过的坑,即使不打算改标签也要把原标签数组原样带上。
更新会自动生成修订记录:
curl -sS -H "Authorization: Bearer $TOKEN" http://localhost:8131/api/posts/2026999042638090240/revisions
实测回显(节选):
[{"id":"2026999337141145600","type":"revision","author":"admin","title":"How to disable public user registration via the REST API?"},{"id":"2026999337359249408","type":"revision","author":"admin","title":"How to disable public user registration in Scoold 1.70?"}]
修订历史的第一条是更新前的原文快照,第二条是更新后的内容。可用 PUT /api/posts/{id}/restore/{revisionid} 回滚到任意修订。
5.5 投票与采纳
投票支持 userid 参数指定投票者(默认为 Token 属主本人)。实测先用管理员 Token 以测试用户 bob 的身份给问题投赞同票:
curl -sS -w '\nHTTP=%{http_code}\n' -X PUT -H "Authorization: Bearer $TOKEN" \
'http://localhost:8131/api/posts/2026999042638090240/voteup?userid=2026999447057076224:profile'
实测回显:HTTP 200,响应体为空。再次读取该问题确认票数:"votes":1。
作者给自己投票会被拒绝:
curl -sS -w '\nHTTP=%{http_code}\n' -X PUT -H "Authorization: Bearer $TOKEN" \
http://localhost:8131/api/posts/2026999042638090240/voteup
实测回显(HTTP 400):
{"message":"Vote request failed.","code":400}
💡 说明:自投票在服务端被硬性拦截(与页面行为一致),对应声望规则中的防刷设计。反对投票用
PUT /api/posts/{id}/votedown,规则相同。
采纳回答(提问者或版主操作):
curl -sS -w '\nHTTP=%{http_code}\n' -X PUT -H "Authorization: Bearer $TOKEN" \
http://localhost:8131/api/posts/2026999042638090240/accept/2026999176172146688
实测回显:HTTP 200,响应体为空。采纳后问题的 answerid 字段回写为被采纳回答的 ID(实测 "answerid":"2026999176172146688"),回答作者获得 scoold.rep_for_accepted_answer(默认 15)声望。
5.6 评论
curl -sS -w '\nHTTP=%{http_code}\n' -X POST \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"comment":"Also see scoold.password_auth_enabled if you want to disable local signups entirely.","parentid":"2026999042638090240"}' \
http://localhost:8131/api/comments
实测回显(节选,HTTP 201):
{"creatorid":"2026970748815413248:profile","comment":"Also see scoold.password_auth_enabled if you want to disable local signups entirely.","parentid":"2026999042638090240","authorName":"admin","id":"2026999548760559616","type":"comment","votes":0}
评论列表(父帖维度分页读取):
curl -sS -H "Authorization: Bearer $TOKEN" \
'http://localhost:8131/api/posts/2026999042638090240/comments?limit=5&page=1'
实测回显(节选,HTTP 200):
[{"creatorid":"2026970748815413248:profile","comment":"Also see scoold.password_auth_enabled if you want to disable local signups entirely.","parentid":"2026999042638090240","authorName":"admin","id":"2026999548760559616","type":"comment","votes":0}]
5.7 标签
标签的增删可以不走整体 PATCH,用专用端点做增量操作:
curl -sS -X PATCH -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"add":["sso"],"remove":["security"]}' \
http://localhost:8131/api/posts/2026999042638090240/tags
实测回显(节选):
{"id":"2026999042638090240","tags":["api","configuration","sso"]}
全站标签清单(含引用计数):
curl -sS -H "Authorization: Bearer $TOKEN" http://localhost:8131/api/tags
实测回显(节选):
[{"id":"tag:configuration","count":2},{"id":"tag:smtp","count":1},{"id":"tag:email","count":1},{"id":"tag:backup","count":1},{"id":"tag:para","count":1},{"id":"tag:api","count":1},{"id":"tag:sso","count":1}]
tag_creation_allowed = false 时普通用户调用 POST /api/tags 建新标签会被拒绝(403 语义),仅管理员/版主可建。
5.8 用户管理
创建用户(仅管理员 Token 可调用):
curl -sS -w '\nHTTP=%{http_code}\n' -X POST \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"bob","email":"bob@scoold.local","identifier":"bob@scoold.local","password":"q1w2e3r4","groups":"user"}' \
http://localhost:8131/api/users
实测回显(HTTP 201):
{"id":"2026999447057076224:profile","name":"bob","email":null,"groups":"users","active":null}
groups 取值 user / moderator / admin,回显中的 users 是内部组名。回显中 email 为 null 是接口对邮箱字段做了脱敏。按关键词查询用户:
curl -sS -H "Authorization: Bearer $TOKEN" 'http://localhost:8131/api/users?q=bob'
实测回显(节选):
[{"id":"2026999447057076224:profile","name":"bob","email":null,"groups":"users"}]
用户相关的运维操作:PUT /api/users/{id}/ban 封禁、PUT /api/users/{id}/moderator 授版主、PUT /api/users/spaces 调整空间归属、GET /api/users/{id}/questions / replies / favorites 查看该用户的提问/回答/收藏。
5.9 搜索
curl -sS -H "Authorization: Bearer $TOKEN" \
'http://localhost:8131/api/search/question/disable'
实测回显(节选):
{"totalHits":0,"page":1,"items":[{"id":"2026999042638090240","title":"How to disable public user registration in Scoold 1.70?","type":"question","tags":["api","configuration","sso"],"votes":1},{"id":"2026999176172146688","title":"How to disable public user registration via the REST API?","type":"reply","parentid":"2026999042638090240"}]}
路径段 /search/{type}/{query} 中 type 支持 question / reply / comment / user / tag 等(可用 GET /api/types 查看全部对象类型)。响应为 {totalHits, page, items} 结构,items 会同时返回命中的问题与回答。搜索按内容分词匹配,totalHits 为精确短语命中数,词条命中以 items 为准。
5.10 空间
curl -sS -H "Authorization: Bearer $TOKEN" http://localhost:8131/api/spaces
实测回显(节选):
[{"id":"scooldspace:default","name":"default","type":"scooldspace"}]
空间是 Scoold 的内容隔离单元,POST /api/spaces 建新空间、PATCH /api/users/spaces 把用户加入空间。带空间属性的写操作(提问等)会校验 Token 属主对该空间的可访问性,无权限返回 404 语义(对调用者隐藏空间存在性)。
六、Para 数据 API 实测(/v1,SigV4 签名)
6.1 签名机制取证
Para /v1 的请求签名采用 AWS Signature Version 4 算法。对服务端签名器(Signer 类)的常量取证结果:服务名 para、区域 us-east-1、时间头 x-amz-date(格式 yyyyMMdd'T'HHmmss'Z')、空请求体的载荷哈希为空串 SHA-256 标准值。Authorization 头格式:
AWS4-HMAC-SHA256 Credential=<accessKey>/<日期>/us-east-1/para/aws4_request, SignedHeaders=host;x-amz-date, Signature=<十六进制签名>
签名派生链:HMAC-SHA256(密钥串"AWS4"+secretKey, 日期) → 区域 → 服务名 → "aws4_request",最后对规范化请求(方法 + 路径 + 查询串 + 头 + 载荷哈希)做终签。
💡 技术拓展:SigV4 是 AWS 全系服务的标准请求签名算法,各语言都有现成实现(Python 的 botocore、Java 的 aws-sdk 等)。Para 自 1.48 起从早期的自定义 HMAC 头切换到 SigV4,因此网上旧教程里的
Authorization: Para key:signature写法在 1.57.0 上会得到 401。
6.2 构造签名并调用(python3 实测)
以下脚本可与 curl 组合完成签名调用(依赖仅标准库):
#!/usr/bin/env python3
# para_sigv4.py —— 构造 Para /v1 请求的 SigV4 签名头
import hashlib, hmac, datetime, sys, urllib.parse
def h(key, msg):
return hmac.new(key, msg.encode() if isinstance(msg, str) else msg, hashlib.sha256).digest()
def sign(method, url_path, host, secret, query=''):
t = datetime.datetime.now(datetime.timezone.utc)
amzdate, datestamp = t.strftime('%Y%m%dT%H%M%SZ'), t.strftime('%Y%m%d')
qs = ''
if query: # 查询参数按参数名排序后参与签名
enc = sorted((urllib.parse.quote(k, safe='~-_'),
urllib.parse.quote(v, safe='~-_'))
for k, v in urllib.parse.parse_qsl(query, keep_blank_values=True))
qs = '&'.join(f'{k}={v}' for k, v in enc)
canonical = '\n'.join([
method, url_path, qs,
f'host:{host}\nx-amz-date:{amzdate}\n', 'host;x-amz-date',
hashlib.sha256(b'').hexdigest()])
scope = f'{datestamp}/us-east-1/para/aws4_request'
sts = '\n'.join(['AWS4-HMAC-SHA256', amzdate, scope,
hashlib.sha256(canonical.encode()).hexdigest()])
k = h(h(h(h(('AWS4' + secret).encode(), datestamp),
'us-east-1'), 'para'), 'aws4_request')
sig = hmac.new(k, sts.encode(), hashlib.sha256).hexdigest()
return amzdate, ('AWS4-HMAC-SHA256 '
f'Credential=app:scoold/{scope}, SignedHeaders=host;x-amz-date, '
f'Signature={sig}')
if __name__ == '__main__':
method, path, host, q = sys.argv[1], sys.argv[2], sys.argv[3], (sys.argv[4] if len(sys.argv) > 4 else '')
secret = sys.argv[5] if len(sys.argv) > 5 else ''
amzdate, auth = sign(method, path, host, secret, q)
print(amzdate)
print(auth)
使用方式(密钥从配置文件读取后经环境变量注入,避免落 shell 历史):
export PARA_SECRET='<scoold.para_secret_key 的值>'
eval $(python3 para_sigv4.py GET /v1/questions localhost:8132 '' "$PARA_SECRET" | awk '{printf "HDR%d=\"%s\" ", NR, $0}')
curl -sS -H "Authorization: $HDR2" -H "x-amz-date: $HDR1" http://localhost:8132/v1/questions
6.3 各端点实测回显
列出对象(GET /v1/{type},type 可为 question/reply/profile/tag/comment 等全部对象类型):
curl -sS -H "Authorization: $HDR2" -H "x-amz-date: $HDR1" http://localhost:8132/v1/questions
实测回显(节选):
{"totalHits":3,"lastKey":"2026999549599420416","page":0,"items":[{"id":"2026971254229045248","appid":"scoold","name":"How to configure SMTP email notifications in Scoold?","tags":["configuration","email","smtp"],"type":"question","answercount":1,"title":"How to configure SMTP email notifications in Scoold?"}]}
注意 totalHits 与 Scoold 业务 API 一样是精确短语命中计数,全量翻页以 lastKey(下一页游标)为准。
标签列表 GET /v1/tags:实测回显(节选):
{"totalHits":7,"items":[{"id":"tag:smtp","appid":"scoold","count":1,"tag":"smtp","type":"tag"}]}
用户资料列表 GET /v1/profiles:实测回显(节选):
{"totalHits":3,"items":[{"id":"2026973759230971904:profile","name":"user2","type":"profile","votes":25}]}
按 ID 读取对象 GET /v1/objects/{id}:实测回显(节选):
{"id":"2026971254229045248","name":"How to configure SMTP email notifications in Scoold?","tags":["configuration","email","smtp"],"type":"question","answercount":1}
全文搜索 GET /v1/search?s=<关键词>:
eval $(python3 para_sigv4.py GET /v1/search localhost:8132 's=SMTP' "$PARA_SECRET" | awk '{printf "HDR%d=\"%s\" ", NR, $0}')
curl -sS -H "Authorization: $HDR2" -H "x-amz-date: $HDR1" 'http://localhost:8132/v1/search?s=SMTP'
实测回显(节选):
{"totalHits":65,"page":0,"items":[{"id":"2026970748974796800","name":"System","type":"scooldactivity","action":"config.change"}]}
Para 搜索作用于全部对象类型(含系统审计对象 scooldactivity),比业务搜索范围大,可用 type:question 这类过滤条件收窄。
⚠️ 注意(实测排障):带查询参数的请求必须把参数纳入签名。实测漏签
s=SMTP时服务端返回 403 与明确提示:{"code":403,"message":"Invalid signature for request GET /v1/search coming from app scoold"}把查询参数按名排序编码后加入规范化请求重新签名即恢复 200。
6.4 错误对照表(实测)
| 场景 | 状态码 | 实测响应 |
|---|---|---|
| 无 Authorization 头 | 401 | {"code":401,"message":"You don't have permission to access this resource. [GET /v1/questions]"} |
| 密钥错误(secret 不匹配) | 401 | 同上,消息不含具体原因,按签名失败统一处理 |
| 查询参数未参与签名 | 403 | {"code":403,"message":"Invalid signature for request GET /v1/search coming from app scoold"} |
| 访问无权限的对象类型 | 403 | 同 Invalid signature 或 permission denied,先核对 accessKey 对应 app 的权限 |
七、分页、过滤与排序通用约定
- 业务 API:列表端点统一用
page(页码,从 1 起)+limit(每页条数,默认 25)+sortby(排序字段)+desc(是否倒序)。例如GET /api/posts/{id}/comments?limit=5&page=1&sortby=timestamp&desc=false。 - 搜索类端点:响应固定为
{totalHits, page, items}包裹结构,业务判断「有没有数据」应检查items而非totalHits。 - Para 数据 API:游标式分页——响应中的
lastKey传回下一次请求即可翻下一页,避免深分页的性能退化。 - 写操作:POST 创建类端点由服务端生成对象 ID(19 位雪花 ID,实测
2026999042638090240);PATCH/PUT/DELETE 更新与删除成功返回 200,POST 创建返回 201 并回带完整对象。
八、常见问题(FAQ)
Q1:PATCH 更新帖子返回 500 {"message":null,"code":500}?
请求体缺 tags 字段。PATCH 端点在服务端把标签数组拼成逗号串后提交,字段缺失直接空指针。解决:请求体始终带上完整 tags 数组(不改标签就传原值)。实测复现两次均复现,补上 tags 后同一请求返回 200。
Q2:投票接口返回 400 Vote request failed.?
三种实测确认的触发条件:作者给自己的帖子投票;重复投同一票(再投一次即撤销,第三次投会失败);被投票的帖子 ID 不存在。逐项排除即可。
Q3:Para 签名调用稳定返回 401?
按顺序核对:accessKey 是否为 app:scoold(或实际配置的 scoold.para_access_key);secretKey 是否为同一密钥对的 secret——app:para 的 root secret 与 app:scoold 的 accessKey 不是一对,混配无法通过校验(实测使用随机错误 secret 请求即返回 401,服务端对「密钥不存在」与「签名不匹配」返回同一错误,不泄露具体原因);x-amz-date 与签名内的日期是否同一时刻生成(时钟偏差过大会被拒);Host 头与实际请求主机是否一致(签名包含 host)。
Q4:个人 API Token 过期后怎么办? 401 后重新登录 Web 界面 → Settings → API keys 撤销旧 Token 并重新生成。生成时可选有效期(1~2200 小时),生产集成建议定期轮换而非使用超长有效期。
Q5:如何确认 API 已成功开启?
GET /api 未开 API 时返回 403,开启后返回 {"healthy":true,...}。重启服务后仍 403 时,检查配置文件中 scoold.api_enabled 是否被管理面板的在线配置覆盖(在线值优先,见《Scoold 1.70 配置参数详解》第九章)。
九、参考链接
- Scoold 官方站:https://scoold.com
- Scoold 源码仓库(ApiController/SettingsController 即本文接口实现):https://github.com/Erudika/scoold
- Para 官方文档(/v1 API 与签名):https://paraio.com/docs
- Para 源码仓库(Signer 签名器实现):https://github.com/Erudika/para
- AWS Signature Version 4 规范:https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html
- 本专栏配套阅读:部署全流程见《Scoold 1.70 Docker Compose 部署指南》,组件选型背景见《Scoold 1.70 介绍与选型指南》,配置参数逐项解释见《Scoold 1.70 配置参数详解》。