Formbricks 常用命令与运维实战

Formbricks 6.1.2 运维工具箱:Management API 全链路实测(密钥两级作用域/问卷 CRUD/响应提交查询/Webhook 签名推送)、健康检查两级探活、psql 盘点与 pg_dump 备份验证,附 API 真实报错排障路径。

FormbricksFormbricks自托管问卷调查2026/10/28

Formbricks 常用命令与运维实战

目标:给 Formbricks 6.1.2 自托管管理员一套可直接执行的运维工具箱——Management API 全链路(密钥认证、问卷与响应管理、Webhook)、健康检查与数据盘点、PostgreSQL 备份与验证,以及 API 真实报错的排障路径。全部命令与回显均在 6.1.2 实测环境逐条跑通;部署与环境变量背景分别见 《Formbricks Docker Compose 部署实战》 与 《Formbricks 6.1.2 配置参数详解》。

一、环境信息

项目值
Formbricks 版本6.1.2(官方 Compose 编排部署)
服务器Ubuntu 22.04,Web 入口 http://192.168.146.128:8129
演示账号admin@formbricks.local / q1w2e3r4
实测数据基线1 个用户、2 个问卷、7 条响应(psql 实测,见 5.2)
API 体系Management API v1(个人密钥)+ Client API(问卷投放端)+ v2/v3 扩展端点

二、工具概览

Formbricks 没有传统的运维 CLI,日常运维通过四类工具完成:

工具载体用途
Management APIcurl + X-Api-Key 请求头问卷 CRUD、响应查询、Webhook 管理、联系人管理等
Client APIcurl(免鉴权)模拟问卷端提交响应(自动化测试、数据灌入)
健康检查端点curl探活与依赖链自检(/health、/api/v2/health)
PostgreSQL 工具psql / pg_dump数据盘点、逻辑备份与验证

密钥体系(关键认知):后台 Settings → API Keys 生成的密钥分两种作用域,行为不同——

  • 工作空间级密钥(绑定单个 workspace):走 /api/v1/management/*,本文实测主力;
  • 组织级密钥(跨 workspace 管理):/api/v1/management/me 会拒绝它,需走 /api/v2/me(实测报错见 4.1)。

密钥形如 fbk_ 前缀字符串,创建时只显示一次,本文用 ${FBK_KEY} 指代——执行前先导出:

export FBK_KEY=fbk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
export BASE=http://192.168.146.128:8129

💡 技术拓展:API 网关常见的三种认证头——Authorization: Bearer(OAuth 2.0 风格,JWT/令牌)、X-Api-Key(静态静态密钥,简单直接)、HMAC 签名(对请求体签名防篡改)。Formbricks Management API 选 X-Api-Key(静态密钥 + HTTPS 传输安全),而 Webhook 推送端选 HMAC 签名(webhook-signature 头,接收端可验签防伪造),两种机制各取所长。

三、前置条件

  1. 已有管理员账号并能进入后台(演示账号见第一章);
  2. 后台 Settings → API Keys 生成一枚 API 密钥并导出为 FBK_KEY(工作空间级);
  3. 服务器上装有 curl 与 PostgreSQL 客户端工具(psql、pg_dump 18.x,随数据库服务提供);
  4. 确认服务健康(见 5.1)。

四、Management API 详解

4.1 认证与身份确认

先确认密钥与归属。工作空间级密钥调 /api/v1/management/me(实测回显):

$ curl -s -H "X-Api-Key: $FBK_KEY" $BASE/api/v1/management/me
{"id":"cmvqyq2yg000101lhttivoi0a","type":"production","createdAt":"2026-10-27T17:31:46.936Z","updatedAt":"2026-10-27T17:31:46.936Z","appSetupCompleted":false,"workspace":{"id":"cmvqyq2yg000101lhttivoi0a","name":"My workspace"},"project":{"id":"cmvqyq2yg000101lhttivoi0a","name":"My workspace"}}

返回的 workspace.id 就是后续所有请求的工作空间 ID(本文记 WS=cmvqyq2yg000101lhttivoi0a)。组织级密钥调同一端点会报(实测):

$ curl -s -w "\nHTTP:%{http_code}\n" -H "X-Api-Key: $FBK_ORG_KEY" $BASE/api/v1/management/me
{"code":"bad_request","message":"This endpoint only supports API keys that are scoped to a single workspace. Use GET /api/v2/me to inspect organization-level API keys or keys with access to multiple workspaces.","details":{}}
HTTP:400

组织级密钥改查 /api/v2/me(实测回显,节选):

$ curl -s -H "X-Api-Key: $FBK_ORG_KEY" $BASE/api/v2/me | head -c 500
{"data":{"workspacePermissions":[{"permissions":"manage","workspaceId":"cmvqyq2yg000101lhttivoi0a","workspaceName":"My workspace"}],"environmentPermissions":[],"organizationId":"cmvqyq2xf000001lh2tpxv8l0","organizationAccess":{"accessControl":{"read":true,"write":true}}}}

⚠️ 注意:报错提示 Use GET /api/v2/me 是排障路标——遇到 400 先核对密钥作用域与端点版本是否匹配,而不是重生成密钥。

4.2 问卷管理 API

列出问卷(实测):

$ curl -s -H "X-Api-Key: $FBK_KEY" $BASE/api/v1/management/surveys | head -c 400
{"data":[{"id":"cmvqyzegp000f01lhprmiuitv","createdAt":"2026-10-27T17:39:01.753Z","updatedAt":"2026-10-27T17:39:15.236Z","name":"客户体验调研","type":"link","workspaceId":"cmvqyq2yg000101lhttivoi0a","createdBy":null,"status":"inProgress","welcomeCard":{"enabled":false,"timeToFinish":true,"showResponseCount":false},"questions":[{"id":"q1","type":"openText","headline":{"default":"你对本产品的整体体验满意吗?请简述"},"required":true,"charLimit":{"enabled":false},"inputType":"text","insightsEnabled":false},{"id":"q2","type":"nps","headline":{"default":"你有多大可能向同事推荐本产品?"},"required":true,"isColorCodingEnabled":false}],"blocks":[{"id":"zitd5d5sjpzbkqqji6lzntc0","name":"Block 1","elements":[{"id":"q1","type":"openText","headline":{"default":"你对本产品的整体体验满意吗?请简述"},"required":true,"charLimit":{"enabled":false},"inputType":"text","insightsEnabled":false}]},{"id":"zb6nyyiy0skbtvgoi5d663io","name":"Block 2","elements":[{"id":"q2","type":"nps","headline":{"default":"你有多大可能向同事推荐本产品?"},"required":true,"isColorCodingEnabled":false}]}],"endings":[],"displayOption":"displayOnce","recontactDays":null,"displayLimit":null,"autoClose":null,"delay":0,"displayPercentage":null,"autoComplete":null,"publishOn":null,"closeOn":null,"archivedAt":null,"isVerifyEmailEnabled":false,"isBackButtonHidden":false,"isAutoProgressingEnabled":false,"isCaptureIpEnabled":false,"isAnonymizeResponsesEnabled"

输出为完整问卷对象数组(含题目、逻辑块、结尾卡配置),做脚本处理时建议接 jq。

创建问卷:请求体最小结构为名称、类型、工作空间 ID 与题目数组。实测经历了两个真实报错,正好演示排障路径——

第一次缺 workspaceId(实测):

$ curl -s -w "\nHTTP:%{http_code}\n" -X POST -H "X-Api-Key: $FBK_KEY" -H "Content-Type: application/json" \
    -d '{"name":"员工满意度速测","type":"link","questions":[...]}' \
    $BASE/api/v1/management/surveys
{"code":"bad_request","message":"workspaceId must be provided","details":{}}
HTTP:400

补上 workspaceId 后第二次同时传了 questions 与 blocks(实测):

{"code":"bad_request","message":"Fields are missing or incorrectly formatted","details":{"blocks":"Survey cannot have both questions and blocks. Use one model.","questions":"Cannot provide both questions and blocks. Please provide only one of these fields."}}
HTTP:400

去掉 blocks 只传 questions,创建成功(实测):

$ curl -s -w "\nHTTP:%{http_code}\n" -X POST -H "X-Api-Key: $FBK_KEY" -H "Content-Type: application/json" \
    -d @api_survey_new.json $BASE/api/v1/management/surveys
{"data":{"id":"cmvrgr2li000201qtn53zlu73","createdAt":"2026-10-28T01:56:26.214Z","updatedAt":"2026-10-28T01:56:26.214Z","name":"员工满意度速测","type":"link","workspaceId":"cmvqyq2yg000101lhttivoi0a","createdBy":null,"status":"draft","welcomeCard":{"enabled":false,"timeToFinish":true,"showResponseCount":false},"questions":[{"id":"q1","type":"openText","headline":{"default":"本周工作中最大的阻碍是什么?"},"required":true,"charLimit":{"enabled":false},"inputType":"text","insig
HTTP:200

其中 api_survey_new.json 为本次实测的请求体(开放题 + 评分题):

{
  "name": "员工满意度速测",
  "type": "link",
  "workspaceId": "cmvqyq2yg000101lhttivoi0a",
  "welcomeCard": {"enabled": false, "timeToFinish": true, "showResponseCount": false},
  "questions": [
    {"id": "q1", "type": "openText", "headline": {"default": "本周工作中最大的阻碍是什么?"}, "required": true, "charLimit": {"enabled": false}, "inputType": "text", "insightsEnabled": false},
    {"id": "q2", "type": "rating", "headline": {"default": "请为本周的整体工作负荷打分"}, "required": true, "range": 5, "scale": "number"}
  ],
  "endings": []
}

发布问卷:草稿状态 draft 不可收集,用 PUT 把 status 改为 inProgress(实测):

$ curl -s -w "\nHTTP:%{http_code}\n" -X PUT -H "X-Api-Key: $FBK_KEY" -H "Content-Type: application/json" \
    -d '{"status":"inProgress"}' \
    $BASE/api/v1/management/surveys/cmvrgr2li000201qtn53zlu73
{"data":{"id":"cmvrgr2li000201qtn53zlu73","createdAt":"2026-10-28T01:56:26.214Z","updatedAt":"2026-10-28T01:56:40.868Z","name":"员工满意度速测","type":"link","workspaceId":"cmvqyq2yg000101lhtti
HTTP:200

查询单个问卷(实测,节选):

$ curl -s -H "X-Api-Key: $FBK_KEY" $BASE/api/v1/management/surveys/cmvrgr2li000201qtn53zlu73 | head -c 300
{"data":{"id":"cmvrgr2li000201qtn53zlu73","createdAt":"2026-10-28T01:56:26.214Z","updatedAt":"2026-10-28T01:56:40.868Z","name":"员工满意度速测","type":"link","workspaceId":"cmvqyq2yg000101lhttivoi0a","createdBy":null,"status":"inProgress","welcomeCard":{"enabled":false,"timeToFinish":true,"sh

验证要点:status 已从 draft 变为 inProgress,链接问卷地址为 $BASE/s/<问卷ID>。

4.3 响应收集与查询 API

Client API 提交响应(免鉴权,模拟受访者提交;实测两条):

$ curl -s -w "\nHTTP:%{http_code}\n" -X POST -H "Content-Type: application/json" \
    -d "{\"surveyId\":\"cmvrgr2li000201qtn53zlu73\",\"finished\":true,\"data\":{\"q1\":\"跨部门对齐会太多,核心开发时间被压缩\",\"q2\":3},\"meta\":{\"source\":\"link\",\"url\":\"$BASE/s/cmvrgr2li000201qtn53zlu73\",\"action\":\"link\"}}" \
    $BASE/api/v1/client/cmvqyq2yg000101lhttivoi0a/responses
{"data":{"id":"cmvrgrdxq000301qtjnv0sgh8","quotaFull":false}}
HTTP:200

$ curl -s -w "\nHTTP:%{http_code}\n" -X POST -H "Content-Type: application/json" \
    -d "{\"surveyId\":\"cmvrgr2li000201qtn53zlu73\",\"finished\":true,\"data\":{\"q1\":\"代码评审排队时间长,希望增加评审人\",\"q2\":4},\"meta\":{\"source\":\"link\",\"url\":\"$BASE/s/cmvrgr2li000201qtn53zlu73\",\"action\":\"link\"}}" \
    $BASE/api/v1/client/cmvqyq2yg000101lhttivoi0a/responses
{"data":{"id":"cmvrgrdyf000401qtbr1j1ydr","quotaFull":false}}
HTTP:200

请求体字段:surveyId(问卷 ID)、finished(是否完成)、data(题目 ID → 答案)、meta(来源与 URL)、可选 userId/language。返回 quotaFull:false 表示未触发配额。

错误场景实测(排障与自动化测试常用):

$ curl -s -w "\nHTTP:%{http_code}\n" -X POST -H "Content-Type: application/json" \
    -d '{"surveyId":"cmnosuchsurvey00000000000","finished":true,"data":{"q1":"x"}}' \
    $BASE/api/v1/client/cmvqyq2yg000101lhttivoi0a/responses
{"code":"not_found","message":"Survey not found","details":{"resource_id":"cmnosuchsurvey00000000000","resource_type":"Survey"}}
HTTP:404

$ curl -s -w "\nHTTP:%{http_code}\n" -X POST -H "Content-Type: application/json" \
    -d '{"surveyId":"not-a-cuid","finished":true,"data":{"q1":"x"}}' \
    $BASE/api/v1/client/cmvqyq2yg000101lhttivoi0a/responses
{"code":"bad_request","message":"Fields are missing or incorrectly formatted","details":{"surveyId":"Invalid cuid2"}}
HTTP:400

Management API 查询响应:注意 v6.1.2 的响应列表是顶层资源(带 surveyId 过滤参数),不是嵌在问卷路径下——实测用错嵌套路径会得到 404:

$ curl -s -H "X-Api-Key: $FBK_KEY" "$BASE/api/v1/management/responses?surveyId=cmvrgr2li000201qtn53zlu73" | head -c 800
{"data":[{"id":"cmvrgrdyf000401qtbr1j1ydr","createdAt":"2026-10-28T01:56:40.935Z","updatedAt":"2026-10-28T01:56:40.935Z","surveyId":"cmvrgr2li000201qtn53zlu73","finished":true,"endingId":null,"data":{"q1":"代码评审排队时间长,希望增加评审人","q2":4},"meta":{"url":"http://192.168.146.128:8129/s/cmvrgr2li000201qtn53zlu73","action":"link","source":"link","userAgent":{"device":"desktop"}},"ttc":{},"variables":{},"contactAttributes":null,"singleUseId":null,"language":null,"displayId":null,"contact":null,"tags":[]},{"id":"cmvrgrdxq000301qtjnv0sgh8","createdAt":"2026-10-28T01:56:40.910Z","updatedAt":"2026-10-28T01:56:40.910Z","surveyId":"cmvrgr2li000201qtn53zlu73","finished":true,"endingId":null,"data":{"q1":"跨部门对齐会太多,核心开发时间被压缩","q2":3},"meta":{"url":"http://192.168.146.128:8129/s/cmvrgr2li000201qtn53zlu73","action":"link","source":"link","userAgent":{"device":"desktop"}},"ttc":{},"variables":{},"contactAttributes":null,"singleUseId":null,"language":null,"displayId":null,"contact":null,"tags":[]}]}

响应字段解析:data 内是题目 ID 到答案的映射(q2 评分题返回数值)、finished 标记完成度、meta 记录来源渠道。做 NPS/评分统计时对该数组按题目 ID 聚合即可。

4.4 Webhook 管理 API

创建 Webhook(实测,接收端为内网测试地址,需配置 DANGEROUSLY_ALLOW_WEBHOOK_INTERNAL_URLS=1,见配置篇 7.4):

$ curl -s -w "\nHTTP:%{http_code}\n" -X POST -H "X-Api-Key: $FBK_KEY" -H "Content-Type: application/json" \
    -d '{"url":"http://192.168.146.128:8139/hook","triggers":["responseFinished"],"surveyIds":[]}' \
    $BASE/api/v1/management/webhooks
{"data":{"id":"cmvrea3xt000001qtxttei8ps","name":null,"createdAt":"2026-10-28T00:47:15.569Z","updatedAt":"2026-10-28T00:47:15.569Z","url":"http://192.168.146.128:8139/hook","source":"user","workspaceId":"cmvqyq2yg000101lhttivoi0a","triggers":["responseFinished"],"surveyIds":[],"secret":"whsec_mUq4dKvdoRNfg4at7oP+QT6IxcDsyU8XUQI6arj8Hbs=","environmentId":"cmvqyq2yg000101lhttivoi0a"}}
HTTP:200

关键返回字段:secret(whsec_ 前缀,用于接收端 HMAC 验签,只显示一次务必保存)、triggers(订阅事件,surveyIds 为空表示全部问卷)。

触发验证:创建后提交一条完成响应(同 4.3 的 Client API 调用),接收端实时收到推送。实测接收端记录到的请求头与载荷(节选):

{"ts": "2026-10-28T09:56:40.969615", "path": "/hook",
 "headers": {"host": "192.168.146.128:8139", "content-type": "application/json",
   "webhook-id": "131096a920ab687a99a141f2387cc97168f76523af6d1eac3b9781a017e1b98d",
   "webhook-timestamp": "1793152600",
   "webhook-signature": "v1,DMJbHo7OuNbplv0xTf5ey0eGX/CeoPh7llVl0osO/9E=",
   "user-agent": "node", "content-length": "755"},
 "body": "{\"webhookId\":\"cmvrea3xt000001qtxttei8ps\",\"event\":\"responseFinished\",\"data\":{\"id\":\"cmvrgsrdx000501qtavges2v6\",...}"}}

三个 webhook-* 头即 Svix 风格签名体系(webhook-signature 为 v1,<HMAC>),接收端用创建时返回的 secret 按 Svix 标准算法验签即可拒绝伪造请求;body 内含完整响应数据与问卷快照(event、data.data、data.survey)。

4.5 健康检查端点

两级探活,浅层与深层(均实测):

$ curl -s http://192.168.146.128:8129/health
{"status":"ok"}

$ curl -s http://192.168.146.128:8129/api/v2/health
{"data":{"main_database":true,"cache_database":true}}

/health 只证明应用进程在响应;/api/v2/health 会实连主数据库与缓存数据库并逐项返回布尔值——监控告警建议绑后者,它能在数据库断连时第一时间翻转。另注:v6 的周期任务(响应清理等)由内置调度器执行,早期版本的 HTTP cron 触发端点在 6.1.2 已不存在(实测 /api/cron/trigger 等路径均 404 回退页面),CRON_SECRET 保留用于兼容外部调度接入。

五、数据盘点与备份

5.1 盘点命令速查

连接业务库做只读盘点(在数据库服务器执行;口令取自部署目录 .env 的 POSTGRES_PASSWORD):

$ psql "postgresql://postgres:<POSTGRES_PASSWORD值>@127.0.0.1:5432/formbricks" -c '\dt'

表清单节选(业务核心表):

             List of relations
 Schema |           Name           | Type  | Owner
--------+--------------------------+-------+----------
 public | Contact                  | table | postgres
 public | Organization             | table | postgres
 public | Response                 | table | postgres
 public | Survey                   | table | postgres
 public | User                     | table | postgres
 public | Webhook                  | table | postgres

数量盘点(实测,与后台界面一致):

$ psql "postgresql://postgres:<POSTGRES_PASSWORD值>@127.0.0.1:5432/formbricks" -t \
  -c "select 'users='||count(*) from \"User\" union all select 'surveys='||count(*) from \"Survey\" union all select 'responses='||count(*) from \"Response\";"
 users=1
 surveys=2
 responses=7

⚠️ 注意:Prisma 生成的表名首字母大写("User"、"Survey"),SQL 里必须带双引号;小写会报 relation "organization" does not exist(实测报错原文)。另两张授权/迁移副库(SpiceDB 库、SAML 预留)不在业务库内,盘点时勿混淆。

5.2 逻辑备份(pg_dump)

标准备份命令与实测产物:

$ pg_dump "postgresql://postgres:<POSTGRES_PASSWORD值>@127.0.0.1:5432/formbricks" > backup_formbricks_$(date +%Y%m%d).sql
$ ls -la backup_formbricks_20261028.sql
-rw-r--r-- 1 root root 256222 Oct 28 09:57 backup_formbricks_20261028.sql

备份文件头部(实测,确认版本与完整性):

$ head -6 backup_formbricks_20261028.sql
--
-- PostgreSQL database dump
--

\restrict bJy8FVNRjvazYYwKVbUXSl6TOtBKvdZlt6DhTtbFapW5uqGXzUzn9Wf7dxbckmD

-- Dumped from database version 18.6 (Debian 18.6-1.pgdg12+2)
-- Dumped by pg_dump version 18.6 (Debian 18.6-1.pgdg12+2)

备份有效性验证(实测三步):

$ grep -c "^COPY\|^INSERT" backup_formbricks_20261028.sql
98
$ grep -c "CREATE TABLE" backup_formbricks_20261028.sql
66
$ tail -3 backup_formbricks_20261028.sql
--
-- PostgreSQL database dump complete
--

dump complete 尾标 + 表结构(66 张表)与数据行(98 个 COPY/INSERT 块)齐备,即备份完整。恢复演练用 psql -d <新库> -f backup_xxx.sql 灌入空库后跑 5.1 的数量盘点比对即可。

⚠️ 备份策略建议:业务库每日一备(pg_dump 自定义格式 -Fc 支持并行恢复更佳);上传文件如启用了本地磁盘存储需另行同步其数据目录;Redis 为可再生的缓存/队列,不纳入备份。

六、注意事项

  1. 密钥只在创建时可见:API Key 与 Webhook secret 均一次性展示,丢失只能重建;
  2. 两套密钥两套端点:组织级密钥走 /api/v2/*,工作空间级走 /api/v1/management/*,混用即 400(见 4.1 实测);
  3. 响应列表端点在顶层:/api/v1/management/responses?surveyId=…,嵌套写法 404(见 4.3 实测);
  4. POST 创建问卷只传 questions 或 blocks 之一(见 4.2 实测);
  5. 表名 SQL 引用要带双引号(见 5.1 实测报错);
  6. 监控绑定深度健康端点 /api/v2/health;
  7. data 键名是题目 ID:问卷重建后题目 ID 会变,依赖 API 做数据管道时先固化问卷再对齐字段。

七、常见问题

7.1 curl 返回一大段 HTML 而不是 JSON

说明请求路径不存在,Next.js 回退渲染了前端页面(实测现象)。逐字符核对端点路径与版本前缀(/api/v1/management/、/api/v1/client/、/api/v2/),并在 curl 后补 -w "\nHTTP:%{http_code}\n" 快速识别 404。

7.2 提交响应成功但列表里查不到

先核对查询参数:/api/v1/management/responses?surveyId= 的 surveyId 必须是问卷 ID 而非工作空间 ID;再确认 finished 状态——进行中(finished:false)的响应默认也会返回,若脚本里加了 finished=true 过滤则只显示已完成条目。

7.3 Webhook 建好了但不推送

依次排查:① 目标 URL 是否内网地址且未开 DANGEROUSLY_ALLOW_WEBHOOK_INTERNAL_URLS(保存时即被拒);② 触发事件是否匹配(responseFinished 只在响应完成时触发,进行中不触发);③ 接收端是否返回 2xx——非 2xx 会进入重试队列,可看 Hub 日志确认投递记录。

7.4 备份恢复后登录不上/数据异常

确认恢复到同名数据库(formbricks)且版本一致(pg_dump 跨大版本恢复需用目标版本的 pg_dump 生成备份);恢复后应用无需重启,刷新页面即可——会话数据在 Redis 中不受 pg_dump 影响是真身不在业务库。

八、参考链接