Scoold 1.70 REST API 使用详解

Scoold 双服务接口体系实测详解:Scoold 业务 API(/api,JWT Token 认证)与 Para 数据 API(/v1,SigV4 签名)双轨拆解,覆盖问答 CRUD、投票、采纳、评论、标签、用户、搜索全流程,含 PATCH 缺 tags 500、自投票 400、SigV4 漏签 query 403 等实测排障实录。

Scoold自托管Java2026/10/30

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 业务 APIhttp://<服务器>:8131/api问答/投票/评论/标签/用户/搜索等业务封装,内置声望、限速、空间权限校验Authorization: Bearer <个人 API Token>脚本、机器人、第三方集成
Para 数据 APIhttp://<服务器>:8132/v1对象存储与检索层,直接读写底层对象(question/reply/profile/tag…)AWS SigV4 请求签名Scoold 服务自身、数据迁移/备份脚本

Scoold 页面上的每一次点击(提问、投票、评论)最终都会转化为一次对 Para 的签名调用。业务 API 是「带规则的门」,数据 API 是「直通仓库的门」——集成开发优先走前者,只有备份、迁移、批量分析等场景才直接调 Para。

3.2 Scoold /api 端点清单(实测归纳)

方法与路径用途实测结果
GET /apiAPI 自检与文档入口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 配置参数详解》第九章)。

九、参考链接