量表开放接口 v1
用一条 API Key 接入 Mindframe 的标准化心理量表: 拉取题目与选项、提交作答、拿到总分、分量表分、分级, 以及危机预警标记。
标准化量表
PHQ-9 抑郁(公共领域)与 ESSA 学业压力(已获授权),题目与分级均来自公开发表的原始量表。
一行请求计分
POST 一组作答,直接返回总分、分级、分量表得分。评分规则由服务端统一维护,不会出现版本分叉。
危机预警内置
PHQ-9 第 9 题(自杀意念)答非零时,响应会带上 crisis.flagged 与求助资源,便于你及时干预。
https://api.mindframe.top
application/json; charset=utf-8。快速开始
获取 API Key
联系 Mindframe 管理员,提供用途名称与使用方,管理员会在后台生成一条 Key。
Key 形如 mfk_ 开头 + 64 位十六进制。
调一次试试
curl https://api.mindframe.top/v1/scales \
-H "X-API-Key: mfk_你的密钥"
返回可用量表列表。看到 "ok": true 就说明鉴权成功了。
取题目 → 收集作答 → 提交计分
# 取 PHQ-9 的题目与选项
curl https://api.mindframe.top/v1/scales/phq9 \
-H "X-API-Key: mfk_你的密钥"
# 提交 9 题的作答(0=完全没有 … 3=几乎每天)
curl -X POST https://api.mindframe.top/v1/scales/phq9/score \
-H "X-API-Key: mfk_你的密钥" \
-H "Content-Type: application/json" \
-d '{"answers":[1,1,2,1,1,2,1,0,0]}'
响应里的 score.total 是总分,band 是分级,crisis.flagged 是危机标记。
鉴权
每个请求都要带上 API Key。支持两种等价的携带方式,任选其一:
| 方式 | 请求头 | 说明 |
|---|---|---|
| 推荐 | X-API-Key: mfk_… | 语义清晰,不与 HTTP 标准认证混用 |
| 标准 | Authorization: Bearer mfk_… | 适合已有 Bearer Token 体系的系统 |
权限范围(scopes)
Key 可以只授予部分权限。管理员生成时会指定:
| Scope | 允许的操作 |
|---|---|
read | 读取量表列表与量表详情(题目、选项、分级标准) |
score | 提交作答并获取分数(含危机标记) |
缺少所需 scope 时返回 403 insufficient_scope。
安全建议
- Key 只放在服务端环境变量里,绝不要写进浏览器 JS 或移动端安装包。
- 不同系统用不同 Key,便于单独停用与统计用量。
- 怀疑泄露时立即联系管理员停用并重新签发。
接口参考
/v1/scales
scope: read
列出全部可计分的量表(不含题目正文,响应体较小)。
请求示例
curl https://api.mindframe.top/v1/scales \
-H "X-API-Key: mfk_你的密钥"
响应示例 200
{
"ok": true,
"count": 2,
"scales": [
{
"id": "phq9",
"name": "PHQ-9",
"item_count": 9,
"score_range": [0, 27],
"window": "过去两周",
"stage": 1,
"time_minutes": 2,
"bands": [
{ "key": "minimal", "label": "无或极轻", "min": 0, "max": 4 },
{ "key": "mild", "label": "轻度", "min": 5, "max": 9 },
{ "key": "moderate", "label": "中度", "min": 10, "max": 14 },
{ "key": "moderately_severe", "label": "中重度", "min": 15, "max": 19 },
{ "key": "severe", "label": "重度", "min": 20, "max": 27 }
]
},
{
"id": "essa",
"name": "ESSA",
"item_count": 6,
"score_range": [6, 30],
"window": "通常情况",
"bands": [
{ "key": "low", "label": "低", "min": 6, "max": 12 },
{ "key": "moderate", "label": "中", "min": 13, "max": 19 },
{ "key": "high", "label": "高", "min": 20, "max": 25 },
{ "key": "very_high", "label": "很高", "min": 26, "max": 30 }
]
}
],
"scale_set": "core15",
"disclaimer": "仅供研究与初筛用途,不构成医学诊断,不能替代执业医师的临床评估。",
"docs": "https://docs.mindframe.top/",
"time": 1792305145
}
/v1/scales/{id}
scope: read
取单个量表的完整定义:题目、选项、分量表、分级区间、文献引用。
路径参数
| 参数 | 类型 | 说明 |
|---|---|---|
id | string | 量表标识,如 phq9、essa。可从列表接口获取。 |
查询参数
| 参数 | 默认 | 说明 |
|---|---|---|
lang | zh | 题目语言:zh 中文 / en 英文 |
响应示例 200
{
"ok": true,
"scale": {
"id": "phq9",
"name": "PHQ-9",
"name_zh": "PHQ-9",
"name_en": "PHQ-9",
"window": "过去两周",
"item_count": 9,
"score_min": 0,
"score_max": 27,
"options": [
{ "value": 0, "label": "完全没有" },
{ "value": 1, "label": "有几天" },
{ "value": 2, "label": "一半以上天数" },
{ "value": 3, "label": "几乎每天" }
],
"items": [
{ "index": 0, "text": "做事时提不起劲或没有兴趣", "reverse": false, "crisis": false },
{ "index": 1, "text": "感到心情低落、沮丧或绝望", "reverse": false, "crisis": false }
],
"subscales": {
"emotional": { "label": "情绪/认知", "items": [0,1,5,6,7], "min": 0, "max": 15 },
"somatic": { "label": "躯体", "items": [2,3,4], "min": 0, "max": 9 }
},
"bands": [ ],
"citation": "Kroenke, K., Spitzer, R. L., & Williams, J. B. W. (2001)…",
"licence": "public_domain",
"stage": 1,
"time_minutes": 2
},
"disclaimer": "仅供研究与初筛用途…",
"time": 1792305038
}
items 顺序就是 answers 数组的下标顺序。
按顺序收集作答即可,不需要自己维护题号映射。
/v1/scales/{id}/score
scope: score
提交一份完整作答,返回总分、分级、分量表分、危机标记。
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
answers | array / object | 是 | 逐题作答。数组按 items[].index 顺序;也可用 {"0":1,"1":2} 形式显式指定下标。 |
lang | string | 否 | 响应中标签语言,zh / en,默认 zh |
422 incomplete_answers 并列出缺失的题号。
取值必须是该量表 options[].value 中的值,否则返回 422 invalid_answers。
请求示例
curl -X POST https://api.mindframe.top/v1/scales/phq9/score \
-H "X-API-Key: mfk_你的密钥" \
-H "Content-Type: application/json" \
-d '{"answers":[1,1,2,1,1,2,1,0,0]}'
响应示例 200
{
"ok": true,
"scale": { "id": "phq9", "name": "PHQ-9" },
"score": {
"total": 9,
"min": 0,
"max": 27,
"percent": 33.3
},
"band": { "key": "mild", "label": "轻度", "range": [5, 9] },
"subscales": {
"emotional": { "label": "情绪/认知", "score": 5, "min": 0, "max": 15 },
"somatic": { "label": "躯体", "score": 4, "min": 0, "max": 9 }
},
"items": [
{ "index": 0, "value": 1, "scored": 1 },
{ "index": 1, "value": 1, "scored": 1 }
],
"crisis": { "flagged": false, "items": [] },
"key": { "id": 4, "name": "验证用 Key" },
"disclaimer": "仅供研究与初筛用途…",
"time": 1792305145
}
字段说明
| 字段 | 说明 |
|---|---|
score.total | 总分(已按量表规则处理反向计分) |
score.percent | 在量表可能区间内的相对位置,便于跨量表比较,不是百分位排名 |
band | 分级结果;量表无官方分界值时该字段为 null |
subscales | 分量表得分与取值范围;无分量表时为空对象 |
items[].scored | 该题在计分方向上的实际值(反向题已折算) |
crisis.flagged | 是否触发危机预警,见 危机预警 |
/health
无需鉴权
探活接口。可用于监控与上线自检,不需要 API Key。
{
"ok": true,
"service": "mindframe-api",
"version": "2.1.0",
"generation": "v2",
"env": "production",
"php": "8.4.6",
"sapi": "fpm-fcgi",
"routes": { "total": 6, "live": 6, "planned": 0 },
"retention_lock_hours": 12,
"time": 1792305145
}
可用量表
PHQ-9 抑郁症状
患者健康问卷抑郁量表,Kroenke 等(2001)。评估过去两周的抑郁症状频率。 本题库使用完整 9 题,含第 9 题自杀意念筛查。
ESSA 学业压力
教育压力量表( adolescents),Sun 等(2011)。本题库使用其中区分度最高的 6 题子集, 覆盖同辈竞争、家庭期望、自我期望三个压力来源。
core15(PHQ-9 与 ESSA 合计 15 题)。
后续若调整集合,列表接口的 scale_set 字段会同步变化,请以接口返回为准,不要硬编码。
计分与分级
所有计分都在服务端完成,调用方只需提交原始作答值。 这样做的原因很实际:量表规则(反向计分、分量表归属、分界值)一旦在客户端重写一份, 迟早会出现「网页算 9 分、接口算 7 分」这类无法追查的问题。
反向计分
个别题目需要反向折算(items[].reverse 为 true)。
折算公式为 min + max − value,由服务端处理,
你提交的永远是该选项的原始值。
分级的解读边界
- PHQ-9 的 0–27 分界值是公开发表的临床分级,可直接引用。
- ESSA 6 题子集的分级是研究者自定,不对应原量表分界值;论文中应报告连续分数与三维度分。
percent只表示「在量表可能区间中的位置」,不是人群百分位。- 分量表分数为探索性描述,不建议单独作为构念报告。
危机预警
PHQ-9 第 9 题询问自杀/自伤意念(items[8].crisis = true)。
该题不计入总分,但只要答非「完全没有」,
响应中的 crisis.flagged 就会变成 true,并附带求助资源。
{
"score": { "total": 12, "max": 27 },
"band": { "key": "moderate", "label": "中度" },
"crisis": {
"flagged": true,
"items": [8],
"message": "有作答指向潜在风险,请尽快向作答者展示求助资源。",
"resources": [
{ "name": "急救", "contact": "120" },
{ "name": "北京心理危机研究与干预中心", "contact": "800-810-1117 / 010-8295-1332" },
{ "name": "全国统一心理援助热线", "contact": "12356" }
]
}
}
crisis.flagged 为 true 时,
必须在用户可见的界面上即时展示求助资源,并(如适用)触发人工回访流程。
这是使用本接口的前提条件。
代码示例
Python
import os, requests
API = "https://api.mindframe.top"
HEADERS = {
"X-API-Key": os.environ["MINDFRAME_API_KEY"],
"Content-Type": "application/json",
}
def get_scale(scale_id: str) -> dict:
r = requests.get(f"{API}/v1/scales/{scale_id}", headers=HEADERS, timeout=15)
r.raise_for_status()
return r.json()["scale"]
def score(scale_id: str, answers: list[int]) -> dict:
r = requests.post(f"{API}/v1/scales/{scale_id}/score",
headers=HEADERS, json={"answers": answers}, timeout=15)
r.raise_for_status()
return r.json()
# 用法
scale = get_scale("phq9")
print("题目数:", scale["item_count"])
result = score("phq9", [1, 1, 2, 1, 1, 2, 1, 0, 0])
print("总分:", result["score"]["total"], "分级:", result["band"]["label"])
if result["crisis"]["flagged"]:
for res in result["crisis"]["resources"]:
print("求助:", res["name"], res["contact"])
JavaScript / Node
const API = "https://api.mindframe.top";
const KEY = process.env.MINDFRAME_API_KEY;
async function scoreScale(scaleId, answers) {
const res = await fetch(`${API}/v1/scales/${scaleId}/score`, {
method: "POST",
headers: {
"X-API-Key": KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ answers }),
});
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new Error(`${res.status} ${err.error ?? "unknown"}: ${err.message ?? ""}`);
}
return res.json();
}
const r = await scoreScale("phq9", [1, 1, 2, 1, 1, 2, 1, 0, 0]);
console.log(r.score.total, r.band?.label);
if (r.crisis.flagged) {
// 必须展示求助资源
console.table(r.crisis.resources);
}
PHP
<?php
$key = getenv('MINDFRAME_API_KEY');
$ch = curl_init('https://api.mindframe.top/v1/scales/phq9/score');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'X-API-Key: ' . $key,
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode(['answers' => [1,1,2,1,1,2,1,0,0]]),
CURLOPT_TIMEOUT => 15,
]);
$body = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
$data = json_decode($body, true);
if ($code !== 200) {
throw new RuntimeException($data['message'] ?? 'API error');
}
echo "总分: {$data['score']['total']}\n";
if (!empty($data['crisis']['flagged'])) {
foreach ($data['crisis']['resources'] as $r) {
echo "求助: {$r['name']} {$r['contact']}\n";
}
}
限流
限流按 API Key 计算,默认每分钟 60 次,可在签发时调整。 超限返回:
{
"ok": false,
"error": "rate_limited",
"message": "该 API Key 每分钟最多 60 次调用,请稍后重试。",
"limit_per_minute": 60,
"retry_after": 60,
"time": 1792305145
}
响应头带 Retry-After,请按该秒数退避重试,不要立即重发。
错误码
所有错误响应结构一致:ok: false + 机器可读的 error + 人类可读的 message。
| HTTP | error | 含义 | 怎么处理 |
|---|---|---|---|
| 400 | bad_json | 请求体不是合法 JSON | 检查 Content-Type 与 JSON 语法 |
| 400 | empty_body | 请求体为空 | 确认发出了 body |
| 400 | missing_answers | 缺少 answers 字段 | 按文档传 {"answers":[…]} |
| 401 | missing_api_key | 没带 Key | 加 X-API-Key 请求头 |
| 401 | invalid_api_key | Key 不存在或已删除 | 核对 Key,或联系管理员重发 |
| 401 | key_disabled | Key 已停用 | 联系管理员启用 |
| 401 | key_expired | Key 已过期 | 联系管理员续期 |
| 403 | insufficient_scope | 缺所需权限 | 申请带 score 或 read 的 Key |
| 404 | scale_not_found | 量表 id 不存在 | 用列表接口拿可用 id |
| 405 | method_not_allowed | 用了错误的 HTTP 方法 | 看响应里的 allow |
| 422 | incomplete_answers | 缺题 | 看 missing_items 补齐 |
| 422 | invalid_answers | 取值越界 | 看 errors[],取值须在 options 内 |
| 429 | rate_limited | 超过限流 | 按 Retry-After 退避 |
| 500 | internal_error | 服务端异常 | 稍后重试;持续失败请联系管理员 |
错误响应示例
{
"ok": false,
"error": "incomplete_answers",
"message": "作答不完整:本量表共 9 题,缺 6 题。",
"missing_items": [3, 4, 5, 6, 7, 8],
"item_count": 9,
"time": 1792305145
}
伦理与合规
使用边界
- 本接口仅供研究与初筛,输出结果不构成诊断、处方或任何医疗建议。
- 不得将分数直接呈现给受测者作为「你患有抑郁症」之类的结论。
- 涉及未成年人时,须遵循当地关于未成年人参与研究的伦理与知情同意规定。
数据责任
- 本接口是无状态的:计分不会保存作答内容,服务端只记录调用次数用于限流统计。
- 作答数据的存储、脱敏与销毁由接入方负责,须符合你所在机构的伦理审查要求。
- 日志中不记录作答正文,但可能记录 Key 的标识与调用时间。
量表版权
- PHQ-9:公共领域,Pfizer 已明确豁免版权,可免费使用。
- ESSA:版权归 SAGE Publications,使用须保留完整文献引用; 其中改编自 AESI 的题目属第三方版权,如你有特殊用途请自行确认。
危机响应义务
见 危机预警。触发时向受测者展示求助资源是接入方的硬性义务, 不是可选项。
更新记录
新增 /v1/scales、/v1/scales/{id}、/v1/scales/{id}/score,
引入 API Key 鉴权与按 Key 限流。服务版本 2.1.0。
问卷提交与记录撤回接口稳定;补全探活信息。