Ghostfolio 3.90.0 API 使用详解

Ghostfolio 3.90.0 自托管 REST API 逐域实测:注册换令牌、账户、活动导入导出、组合持仓与 v2 绩效接口、自建资产档案,每条请求附真实响应与错误码

Ghostfolio财务记账自托管REST API2026/10/23

Ghostfolio 3.90.0 API 使用详解

目标:逐域实测 Ghostfolio 3.90.0 自托管 REST API——注册换令牌、账户、活动导入导出、组合分析与 v2 绩效接口,每条请求附真实响应。全部示例已在自托管实例(Ubuntu 22.04 + Docker Compose 栈)用 curl 实测通过。

一、环境信息

  • Ghostfolio:3.90.0(Docker 镜像 ghostfolio/ghostfolio:3.90.0 部署,API 基址 http://127.0.0.1:3333)
  • 操作系统:Ubuntu 22.04 LTS(x86_64)
  • 客户端工具:curl 7.81.0(系统自带)+ python3 -m json.tool(格式化)
  • 认证样例:JWT 变量存放登录令牌(获取方式见四、)
  • 接口前缀:/api/v1(绝大部分路由)与 /api/v2(绩效等新路由,见 3.1)

二、前置条件

2.1 端点与客户端

API 与 Web 同端口同进程:http://<服务器IP>:3333/api/...。本文全部用系统 curl 发请求(无需额外 SDK),健康检查确认服务可用:

curl -s http://127.0.0.1:3333/api/v1/health
{"status":"OK"}

2.2 认证方式总述

两种凭据,全部经 Authorization: Bearer 头传递:

  1. authToken(JWT):登录后签发,有效期 30 天(实测签发载荷 "exp":签发+30天),API 调用首选;
  2. accessToken(128 位访问令牌):注册时一次性领取的用户凭据,用于 Web 端登录与「令牌换发 JWT」。

Ghostfolio 3.90.0 无静态 API Key 体系(POST /api/v1/api-keys 属 Premium 权益,自托管实测 403,见八、8.3),机读一律走 JWT。

三、API 总览

3.1 版本与路由形态(实查源码控制器清单)

3.90.0 后端共 30+ 个控制器,主前缀 /api/v1。已实查的完整域清单:auth、user、account、account-balance、activities、activity、import、export、portfolio、access、asset-profiles、assets、asset、market-data、symbol、tags、watchlist、benchmarks、platform(s)、info、health、api-keys、admin、admin/queue、cache、logo、subscription、ai、public、data-providers/ghostfolio。

@Version('2') 标注的 v2 路由(实查源码):

  • GET /api/v2/portfolio/performance(组合绩效)
  • data-providers/ghostfolio 域 5 个端点(Premium 数据源 API)

⚠️ 混用版本会得到误导性 404:GET /api/v1/portfolio/performance 实测返回 Cannot GET ...,正确路径是 /api/v2/portfolio/performance。

3.2 认证与通用错误码约定

  • 未带令牌访问受保护端点 → 401 {"message":"Unauthorized","statusCode":401};
  • 令牌无效/盐变更后旧令牌 → 403 {"message":"Forbidden"};
  • 请求体校验失败 → 400 {"error":"Bad Request","message":[...]}(message 为字段级错误数组);
  • 路由不存在 → NestJS 标准 404 {"message":"Cannot GET /api/v1/xxx","error":"Not Found","statusCode":404};
  • 限流:注册端点挂 CustomThrottlerGuard(THROTTLE_SIGNUP_LIMIT 控制),触发后返回 429。

四、认证域:注册与登录

4.1 注册(POST /api/v1/user)

自托管注册即「创建用户 + 领取访问令牌」,无邮箱字段;首个注册用户自动成为管理员:

curl -s -X POST http://127.0.0.1:3333/api/v1/user -H 'Content-Type: application/json' -d '{"languageCode":"en"}'

实测回显:

{"accessToken":"da5e8b1e580837b49ddb7e98ddb9e32104d2862da46b5fd4320df9e2e4dab851c099bfc71eb9a3fa026e016a1bdb089e29b24b2ddf73f70864aded4759e4bb0d","role":"ADMIN","authToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjI1OTk0ODEzLTMwNGMtNGNmMy04ZGM2LWNjMjRkYjA3OTAzNSIsImlhdCI6MTc5MjczOTk3MCwiZXhwIjoxNzk1MzMxOTcwfQ.TINiCPrIvIgzpnvUFWOMVguKpKOtvs8ANvrwxyVCGn8"}

字段说明:accessToken 为 128 位十六进制用户凭据(等价密码,妥善保存);authToken 即后续 API 用的 JWT。

传不认识的字段会被 400 拒绝(实测传 email/fullName/password 返回 "property email should not exist" 等三条校验错误)。

4.2 访问令牌换发 JWT(POST /api/v1/auth/anonymous)

JWT=$(curl -s -X POST http://127.0.0.1:3333/api/v1/auth/anonymous -H 'Content-Type: application/json' -d '{"accessToken":"<你的128位令牌>"}' | python3 -c 'import json,sys;print(json.load(sys.stdin)["authToken"])')
echo ${#JWT}

实测回显:

187

错误路径实测:令牌错误时返回 {"statusCode":403,"message":"Forbidden"};ACCESS_TOKEN_SALT 被更换后同样 403(盐参与令牌哈希,实测见配置篇 5.3)。

五、账户域(/api/v1/account)

5.1 查询账户列表(GET /api/v1/account)

curl -s http://127.0.0.1:3333/api/v1/account -H "Authorization: Bearer $JWT" | python3 -m json.tool | head -12

实测回显(注册时自动创建的默认账户):

[
  {
    "comment": null,
    "createdAt": "2026-10-23T07:19:30.217Z",
    "currency": "USD",
    "id": "0ae9c74f-25d0-4693-8289-43d35d04d97f",
    "name": "My Account",
    "platformId": null,
    "userId": "25994813-304c-4cf3-8dc6-cc24db079035"
  }
]

5.2 创建账户(POST /api/v1/account)

curl -s -X POST http://127.0.0.1:3333/api/v1/account -H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' -d '{"name":"券商A","currency":"USD","platformId":null,"comment":"D5 实测账户"}'

实测回显:

{"comment":"D5 实测账户","createdAt":"2026-10-23T07:40:04.996Z","currency":"USD","id":"79478b88-485e-43e8-93cf-0e6258480bb4","name":"券商A","platformId":null,"updatedAt":"2026-10-23T07:40:04.996Z","userId":"2599481..."}

同名域还提供 PUT /api/v1/account/:id(更新)、DELETE /api/v1/account/:id(删除)、GET /api/v1/account-balance(现金余额)。

六、活动与导入导出域

6.1 导入活动(POST /api/v1/import)

请求体四类数组:accounts、activities、assetProfiles、platforms、tags(后四者可选/必填混合,activities 必填)。活动字段:type(BUY/SELL/DIVIDEND…)、symbol、dataSource、date、quantity、unitPrice、fee、currency。

成功导入两笔(自建资产,创建方式见 8.1):

curl -s -X POST http://127.0.0.1:3333/api/v1/import -H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' -d '{
  "activities": [
    {"type":"BUY","symbol":"GF_QZFUND1","dataSource":"MANUAL","date":"2025-01-15T00:00:00.000Z","quantity":100,"unitPrice":10.5,"fee":2,"currency":"USD","comment":"建仓"},
    {"type":"DIVIDEND","symbol":"GF_QZFUND1","dataSource":"MANUAL","date":"2025-06-30T00:00:00.000Z","quantity":100,"unitPrice":0.15,"fee":0,"currency":"USD"}
  ]
}' | python3 -m json.tool | head -22

实测回显(节选):

{
  "activities": [
    {
      "comment": "建仓",
      "currency": "USD",
      "date": "2025-01-15T00:00:00.000Z",
      "fee": 2,
      "id": "74d944d8-0ce1-46a5-833c-f89e91dec61e",
      "quantity": 100,
      "type": "BUY",
      "value": 1050,
      "valueInBaseCurrency": 1050
    }
  ]
}

典型 400 错误实测(YAHOO 数据源无法解析符号,受限网络环境):

{"error":"Bad Request","message":["activities.0.symbol (\"AAPL\") cannot be resolved by the data source (\"YAHOO\")"]}

自建资产符号缺 GF_ 前缀实测:

{"error":"Bad Request","message":["activities.0.symbol (\"GF_QZFUND1\") must be a UUID or start with the prefix \"GF_\" for the data source (\"MANUAL\")"]}

6.2 导出全量数据(GET /api/v1/export)

curl -s http://127.0.0.1:3333/api/v1/export -H "Authorization: Bearer $JWT" | python3 -c "import json,sys; d=json.load(sys.stdin); print('meta:',d['meta']['version'],'| activities:',len(d['activities']),'| accounts:',len(d['accounts']))"

实测回显:

meta: 3.90.0 | activities: 2 | accounts: 1

导出 JSON 与导入格式同构,可直接回灌另一实例(跨实例迁移的官方姿势)。

同域还有 GET/POST/PUT/DELETE /api/v1/activities(单笔活动 CRUD,导入接口之外细粒度维护用)。

七、组合分析域(/api/v1/portfolio、/api/v2/portfolio/performance)

7.1 持仓明细(GET /api/v1/portfolio/holdings)

curl -s http://127.0.0.1:3333/api/v1/portfolio/holdings -H "Authorization: Bearer $JWT" | python3 -c "import json,sys; h=json.load(sys.stdin)['holdings'][0]; print(h['assetProfile']['symbol'], h['assetProfile']['name'], '| 数量', h['quantity'], '| 市值', h['valueInBaseCurrency'], '| 占比', h['allocationInPercentage'])"

实测回显:

GF_QZFUND1 青竹全球ETF | 数量 100.0 | 市值 1050.0 | 占比 1.0

7.2 组合总览(GET /api/v1/portfolio/details)

curl -s "http://127.0.0.1:3333/api/v1/portfolio/details?range=max" -H "Authorization: Bearer $JWT" | head -c 300

实测回显(节选):

{"accounts":{"0ae9c74f-25d0-4693-8289-43d35d04d97f":{"balance":0,"currency":"USD","name":"My Account","valueInBaseCurrency":0}},"createdAt":"2026-10-23T07:20:38.173Z","hasError":false,"holdings":[],...}

7.3 组合绩效(GET /api/v2/portfolio/performance)——注意 v2

curl -s "http://127.0.0.1:3333/api/v2/portfolio/performance?range=max" -H "Authorization: Bearer $JWT" | python3 -m json.tool | head -14

实测回显(导入 1050 美元建仓后):

{
  "chart": [],
  "hasErrors": false,
  "performance": {
    "currentNetWorth": 1050,
    "currentValueInBaseCurrency": 1050,
    "dividendInBaseCurrency": 15,
    "netPerformance": 15,
    "netPerformancePercentage": 0.01428571428571429,
    "totalInvestment": 1050
  }
}

range 取值:today/wtd/mtd/ytd/1y/5y/max;同域还有 GET /api/v1/portfolio/dividends(分红)、/investments(投入)、/report(静态风险分析)、/holding/:dataSource/:symbol(单持仓详情)。

八、管理与资产档案域

8.1 自建资产档案(POST/PATCH /api/v1/admin/profile-data/:dataSource/:symbol)

创建(符号必须 GF_ 前缀):

curl -s -X POST "http://127.0.0.1:3333/api/v1/admin/profile-data/MANUAL/GF_QZFUND1" -H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' -d '{"currency":"USD"}' | head -c 200

实测回显(节选):

{"currency":"USD","dataGatheringFrequency":"DAILY","dataSource":"MANUAL","symbol":"GF_QZFUND1",...}

更新名称与分类(PATCH,实测返回 200):

curl -s -X PATCH "http://127.0.0.1:3333/api/v1/admin/profile-data/MANUAL/GF_QZFUND1" -H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' -d '{"name":"青竹全球ETF","assetClass":"EQUITY","assetSubClass":"ETF"}' -o /dev/null -w '%{http_code}\n'
200

8.2 管理面板数据(GET /api/v1/admin)

curl -s http://127.0.0.1:3333/api/v1/admin -H "Authorization: Bearer $JWT" | head -c 320

实测回显(节选):

{"activitiesCount":2,"dataProviders":[{"dataSource":"COINGECKO","isPremium":false,"name":"CoinGecko"},{"dataSource":"GHOSTFOLIO","isPremium":true,"name":"Ghostfolio"},...]}

8.3 API Key 的权限边界(POST /api/v1/api-keys 实测 403)

curl -s -X POST http://127.0.0.1:3333/api/v1/api-keys -H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' -d '{}'

实测回显:

{"statusCode":403,"message":"Forbidden"}

实测自托管 ADMIN 用户 permissions 数组中不含 createApiKey——数据源 API Key(GHOSTFOLIO 官方数据源)属 Ghostfolio Premium 订阅权益;自托管免费层机读一律走 30 天 JWT(四、)。

8.4 用户与系统信息

curl -s http://127.0.0.1:3333/api/v1/user -H "Authorization: Bearer $JWT" | head -c 260

实测回显(节选,权限矩阵):

{"activitiesCount":2,"id":"25994813-304c-4cf3-8dc6-cc24db079035","permissions":["accessAdminControl","accessAssistant","accessHoldingsChart","createAccess","createAccount","createAccountBalance","createActivity",...]}
curl -s http://127.0.0.1:3333/api/v1/info

实测回显:

{"benchmarks":[],"globalPermissions":["enableAuthToken","createUserAccount"],"baseCurrency":"USD","currencies":["USD","USX"]}

/api/v1/info 无需认证,可作为客户端探测实例能力的探针(globalPermissions 反映注册是否开放等)。

九、通用约定

9.1 过滤参数

组合分析域端点普遍支持过滤器查询参数(源码 GetPerformanceDto 实查):accounts(按账户)、assetClasses(资产大类)、dataSource、symbol、tags、groupBy(月/年聚合)、range(时间范围)、withExcludedAccounts。示例:只看指定账户的绩效:

curl -s "http://127.0.0.1:3333/api/v2/portfolio/performance?range=max&accounts=<账户ID>" -H "Authorization: Bearer $JWT" -o /dev/null -w '%{http_code}\n'
200

9.2 分页与响应形态

实测 3.90.0 组合/导出类端点为全量返回、无分页参数(个人财富管理场景数据量可控);活动列表经 /api/v1/activities 查询时支持按账户/标签/日期过滤。批量维护活动优先走导入接口(一次可挂数百笔,官方 CSV 导入亦经同一后端链路)。

9.3 数据源取值

活动与资产档案的 dataSource 只能取实例支持的值(实测为 YAHOO、MANUAL、COINGECKO,GHOSTFOLIO 需 Premium Key),传其他值在符号解析阶段 400。

十、常见问题(FAQ)

Q1:/api/v1/portfolio/performance 返回 404? 该路由在 v2:GET /api/v2/portfolio/performance。源码中该端点标注 @Version('2'),v1 前缀下不存在(实测对比见 3.1)。

Q2:注册接口传邮箱密码报错? 自托管注册不接受邮箱/密码字段(实测 400 property email should not exist),Ghostfolio 用户体系基于访问令牌(+可选 OIDC/Passkey),经 Web 界面或 POST /api/v1/user 创建。

Q3:令牌突然全部 403? 先查 ACCESS_TOKEN_SALT 是否被改动(盐变即凭据失效,实测见配置篇);再查 JWT_SECRET_KEY 是否变更(JWT 全部过期,重新用 accessToken 换发即可)。

Q4:导入报 symbol cannot be resolved? 默认数据源 YAHOO 需外网可达。受限网络改自建资产(8.1 建 GF_ 档案)或配 CoinGecko Key(配置篇 5.6)。

Q5:authToken 过期怎么办? 30 天有效期(签发载荷可解出 exp)。用保存的 accessToken 重新 POST /api/v1/auth/anonymous 换发;accessToken 本身遗失且无法登录时,管理员可在 Web 界面「用户」页为其重置访问令牌。

十一、参考链接