未登录
新窗口打开 ↗

在 cfapi.aog.cc 开发你的应用插件

这是一份按 2026-11-05 线上源码核对的开发文档。先根据账号层级选路径,能避免绝大多数“按文档做了却 403 / 上传失败 / 审核后没有运行”的问题。

先看清自己的层级与能做的事。
账号层级私有开发(自建自用)市场投稿内部能力
external 外部开发者(默认)不能创建、上传或启用私有插件可以:上传包 → 提交审核,批准后进入市场不可用
internal 普通内部不能写:仅只读 plugin.view可投稿只读查看与安全调用
internal_advanced 高级内部可以管理归属于自己的插件可投稿含沙箱等受控写能力

层级由后台管理员设置,登录令牌里的 tier 只是展示;服务端实时读库,降级后最迟约 20 秒生效。自己签发的 API 密钥不能把层级抬高到超过账号本身。

数据与 KV
文档集合、Schema、带 TTL 的键值
对象存储与文件
插件私有桶、受控文件读写
插件自有用户
独立用户目录;当前登录不签发 API 令牌
AI 网关
统一模型调用与用量查询
事件与任务
Hook、UI Slot、RPC、队列、调度
两种交付形态
平台托管静态前端 / 平台反代你的后端

按目标选路线

目标正确路线不要做什么
我想把插件发布给所有人外部账号登录 → 准备 local 或 remote 包 → 市场上传 → 提交审核 → 管理员批准 → 由具备 plugin.manage 的实例管理员从市场安装不要申请 plugin.manage 企图直接上传安装;外部账号没有这个权限,市场投稿也不需要它
我在平台内部,自建自用internal_advanced 账号 → 在「我的插件」注册/上传 → 启用 → 调试不要用未归属于自己的插件 code,覆盖同名插件会被拒绝
我要调用已上架插件先确认目标插件已 approved 且启用;调用者仍需具备对应的 plugin.view(读)或 plugin.manage(写)不要假设“市场可见”等于“无需授权即可调用”;仅 external 层级本身不会自动带来权限码
30 秒判断你需要哪个包:页面与静态资源在浏览器运行 → local;业务代码跑在你自己的服务器 → remote。两种包都以根目录(或唯一顶层目录)下的 plugin.json 为清单。remote 通过审核后,原提交者可取得本插件的专属验签密钥,用于验证平台签名;不需要也不应该索取平台全局密钥。

给 AI 编码助手的直接入口

把下面地址提供给 AI,比让它猜页面文字可靠。全部为公开、机器可读资料:

AI 索引(先读)/docs/devcenter/llms.txt
完整开发上下文/docs/devcenter/llms-full.txt
结构化机器上下文/docs/devcenter/ai-context.json
唯一 API 合同/api/openapi.json

文档版本与变更记录见 CHANGELOG。机器合同的请求体覆盖边界也在 AI 索引中明确标注,不要跳过。

开发指南

零、第一步:拿到平台账号

开发者中心本身不创建账号,也没有「用第三方登录直接成为开发者」的通道。按你的身份选一条:

# 1) 注册(用户名 3-32 位字母/数字/下划线/连字符;密码 8-64 位且含字母和数字)
curl -sS -X POST https://cfapi.aog.cc/api/app/register \
  -H 'Content-Type: application/json' \
  -d '{"username":"dev_user","password":"Abc12345","nickname":"开发者"}'

# 2) 登录,token 在 data.token
curl -sS -X POST https://cfapi.aog.cc/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"username":"dev_user","password":"Abc12345"}'

注册与登录都按 IP 限流(默认注册 5 次/分钟、登录 10 次/分钟),超限返回 429/42900。若注册返回 403「当前已关闭开放注册」,说明平台关闭了自助注册,只能由管理员开通——这不是你请求写错了。

一、你能开发哪些形态

想做的形态登记类型真实运行方式
网页 / H5 / 单页应用localZIP 解压到插件目录,平台同源静态托管;默认入口是 index.html
前后端分离的网站remote只登记 base_url;平台把 /api/plugins/{code}/proxy/** 转发到你的服务器
微信 / 支付宝小程序、APP、桌面软件remote客户端界面在各自平台;业务后端由你托管,经平台代理或直接调 REST
纯后端能力 / 定时任务remote进程在你自己的服务器,平台不托管其运行
受控脚本 / WASMwasm 或 sandbox登记类型只是标签。必须另建沙箱配置并处于 active 才可执行;当前是内部受控能力,不是外部开发者自助运行环境
不要把五种类型理解成五个执行环境。wasm/sandbox 不会因为注册类型就自动创建沙箱、更不会自动执行;市场上架批准也不会自动把插件放进沙箱。当前真实可自助发布的交付形态是 local 与 remote,其余属于内部运维能力。

二、接入前必须知道的

平台域名https://cfapi.aog.cc
接口根地址(双前缀,同一批路由)/api 或 /api/v1
传输HTTPS;平台拒绝内网/回环 base_url
local 托管根/plugins/{code}/(没有 api 前缀)
remote 代理根/api/plugins/{code}/proxy/(读写权限不同)
插件包上限私有安装默认 50 MB;市场投稿 500 MB(不同接口,勿混用)
响应上限与超时代理响应 16 MB;代理默认 15 秒(可运维配置)

三、跨域(最容易踩的坑)

平台跨域是白名单制,默认只放行本站。若你的前端部署在自己域名、想直接 fetch 平台接口,浏览器会拦截。

解决(二选一):① 让平台方把你的域名加入后台「设置 → cors_allowed」;② 用 remote 类型,通过平台反向代理 /api/plugins/{code}/proxy/...(服务端到服务端,不受跨域限制,最省心)。

提示:本开发者中心与你同域,调接口无跨域问题,可直接测试。

四、身份、权限与三套凭据

先记结论:登录令牌不等于权限。所有需要权限码的接口,都会先校验登录身份,再校验权限码;API 密钥还多一道 scope 闸门。身份合法但缺权限,返回 403,不是重新登录就能解决。

凭据取得方式用途不能做什么
用户登录 JWTPOST /api/auth/login,取 data.token管理端页面、插件市场投稿、需要用户身份的调用不会自动获得插件管理权限;不能绕过层级
API Key后台或个人中心签发,形如 ak_... 的服务端密钥服务端到服务端调用scope 只是前置闸门;仍受账号层级与 RBAC 约束,不能提权
插件作用域令牌POST /api/plugins/{code}/token,使用平台下发的插件密钥做 HMAC 签名插件后端在最小 scope 内调平台能力只能申领管理员已授权且未过期的 scope;只对所属插件生效

前台账号的开发者层级:external(默认)、internal(只获 plugin.view)、internal_advanced(获 plugin.view、plugin.manage、dev.manage)。治理权限 plugin.govern 不在任何开发者层级里,只由后台 RBAC/超管持有。

插件自有用户账号与平台账号完全隔离:终端用户注册 POST /api/plugins/{code}/users/register,登录 POST /api/plugins/{code}/users/login。当前登录接口只校验凭据并返回 user_id、display_name、logged_in_at,不会签发访问令牌;不要把它当平台 API 的授权凭据。平台 API 的身份仍来自平台 JWT/API Key 或插件作用域令牌。

不要把平台登录令牌交给第三方插件页面,也不要把插件密钥、API Key 发到浏览器。数据岛归属由平台调用者身份决定,不由插件自有用户的 username 决定。

前端安全红线:local 插件的 HTML/JS 对访问者可读。任何 API Key、插件密钥、plugin.manage 令牌都不得写进前端包、localStorage 或公开仓库。浏览器端只持有终端用户自己的登录态;高权限调用必须放在受信任服务端。

五、统一响应规范

{ "code": 0, "message": "ok", "data": { } }
HTTPcode含义处理
2xx0成功读 data
40040000参数错误检查请求体/路径
40140100未认证或令牌过期重新登录换 token,不要重试同一枚过期令牌
40340300无权限:缺权限码、层级不足或不是归属人核对权限码与层级,换密钥无效
40440400资源不存在检查 code / ID,或目标插件未启用
40940900冲突:重复提交、用户名已存在、状态不允许先查当前状态再决定是否重试
42942900触发限流(按 IP 或账号的滑动窗口)退避后重试,减少并发;不要换密钥硬刷
50050000服务端内部错误带时间点与请求路径报障
50350300服务暂不可用(升级/保护/依赖不可达)稍后重试,不要当成权限问题

限流是 429/42900,不是 403。403 只表示权限与归属问题;把 429 当 403 去换密钥或改权限,只会继续被限流。失败响应固定为 { "code": 非0, "message": "...", "data": null },程序分支请依据 code 与 HTTP 状态码,不要解析 message 文案。

分页与列表约定

列表接口统一用 page + page_size,不是 offset/limit 混用:

参数/字段含义默认与边界
page页码,从 1 开始默认 1
page_size每页条数默认 20;服务端夹到 1–100,传 1000 也只返回 100
data.items当前页数据空结果时是 [],不是 null
data.total符合条件的总条数用于算总页数:ceil(total / page_size)
data.page / data.page_size服务端实际采用的页码与页大小以返回值回显为准,不要假设等于入参

插件数据集合、插件用户列表、后台列表均遵循这套约定;少数按游标或固定上限的接口会在该接口的 OpenAPI 描述里单独说明。翻页期间数据可能变化,需要强一致时按 id 递增拉取并去重。

幂等与重试

  • 可安全重试:GET、以及语义幂等的 PUT(改状态、改配置、设置归属);网络超时后重发不会产生重复对象。
  • 不要盲目重试:POST 创建类(市场投稿、上传、插件数据新增、发起扣费)。超时只说明你没收到响应,不说明服务端没执行;应先查询当前状态(如 GET /api/market/submissions)再决定是否重发。
  • 撤回是删除:POST /api/market/submissions/{code}/withdraw 会移除这条 pending 提交,不会保留草稿;撤回后要重新走 POST /api/market/submit。
  • 重试节奏:只对 429/42900、500/50000、503/50300 与网络错误退避重试;400/401/403/404/409 重试多少次结果都一样。建议指数退避并加随机抖动,单请求最多重试 3 次。

六、5 种插件类型(登记标签)

type含义实际执行/托管
local静态资源包,解压到插件目录平台静态服务托管;不执行服务端代码
remote远程服务,登记 base_url平台仅反向代理,进程与数据责任在你
wasmWASM 运行时标签需另建沙箱配置并处于 active;默认 wasmi,不是挂上类型就能跑
sandbox进程沙箱标签需另建配置并受内部策略控制;隔离强度取决于实际配置与宿主能力
disabled停用状态不对外提供页面或代理

七、发布与安装是两条不同的链

链路接口身份/权限结果
内部私有安装POST /api/plugins/upload?code={code},body 是 ZIP 原始字节需 plugin.manage;实际只有 internal_advanced 等具备文件落地并注册,可自行启用
市场投稿上传POST /api/market/upload?plugin_code={code},multipart/form-data,字段 file任意有效登录身份;不需要 plugin.manage;校验归属返回本地 download_url,尚未上架
提交审核POST /api/market/submit有效登录身份 + 验证码;每用户每分钟最多 3 次状态 pending
管理员批准POST /api/market/submissions/{id}/approveplugin.govern写入市场版本;不会自动安装、启用或启动沙箱
实例管理员安装GET /api/plugins/market → POST /api/plugins/market/install浏览需 plugin.view;安装需 plugin.managelocal 拉包落地;remote 只登记,归属平台

清单统一使用 plugin.json。必备字段是 code,且必须与上传 query / 市场 plugin_code 完全一致;支持 name、version、type、base_url、description,以及 entry、dependencies、requirements、permissions。ZIP 可含唯一顶层目录,服务端会自动上提;符号链接会被清除。

entry 不会改变 local 页面入口。静态托管固定以目录下 index.html 作为入口;请把你希望访问的首页命名为 index.html。plugin.toml 不是本平台的上传清单。

可下载 plugin.json JSON Schema 做本地校验。内部管理接口(启用/停用/卸载)分别为 POST /api/plugins/{code}/enable、POST /api/plugins/{code}/disable、DELETE /api/plugins/{code},都需要 plugin.manage 与归属校验。

八、平台能力与访问边界

插件能力接口位于 /api/plugins/{code}/...;它们不是“有 external 密钥就能全调”。能否调用取决于账号层级 + 权限码 + 插件 scope 授权 + 目标插件状态。

能力典型端点开发边界
文档集合 / Schema / KV/data/{collection}、/kv/{key}需插件身份与权限;按插件 + 用户数据岛隔离
对象存储 / 文件/storage/objects、/files文件上传 multipart,不是 JSON
插件用户/users/register、/users/login独立用户目录;登录不签发 token,不用于平台 API 鉴权
AI / 支付 / 推送/ai/chat、/payments、/push受插件 scope、账号层级与平台配额共同约束
沙箱执行/sandbox/exec仅具备权限且已配置 active 沙箱时可执行;默认网络 deny、文件系统 readonly

已核对的插件端点和参数以 OpenAPI 3.1 为准;页面「接口文档」按路径搜索,交互式调试用 Swagger。OpenAPI 中标记 internal-admin-generic 的内部运维接口不构成对外开发承诺。

八·补、调用其他插件的功能(组装式开发)

你开发自己的插件时,不需要自己实现所有能力——可以直接调用平台上已上架、已开放外部调用的插件。 这就是「组装式开发」:别人做好的图片库、表单生成、监控面板等,你一行请求就能用。

① 发现可调用插件

打开本页顶部「可调用插件」标签,它实时拉取 GET /api/plugins/callable, 列出所有 approval_status=approved 且已启用的插件,每张卡片都带可复制的调用示例。

② 调用约定

只有 remote 且配置了 base_url 的插件可经代理调用。local 插件没有后端,访问其 /proxy 会返回“未配置后端地址”,不要把它当 API 服务。

GET  /api/plugins/<对方code>/proxy/<对方暴露的路径>   # 需要权限码 plugin.view
POST /api/plugins/<对方code>/proxy/<对方暴露的路径>   # 需要权限码 plugin.manage
Authorization: Bearer <有对应权限的登录令牌或 API Key>
Content-Type: application/json

{ "arg1": "value1" }

代理仅接受 GET / HEAD / OPTIONS / POST / PUT / DELETE;请求 query 会原样透传,响应上限 16 MB,默认 15 秒超时。

③ 权限与配额(最容易误判的一段)

  • “市场可见”不等于“无需授权”。读调用仍要求调用者具备 plugin.view,写调用要求 plugin.manage。插件处于 approved 只说明它可被列为目录项,不会替你补齐权限码。
  • 仅有 external 层级不会自动获得任何权限码;API Key 的 scope 只是第一道闸门,仍要经过层级/RBAC 判定。若调用返回 403,先核对权限码,不要反复换密钥。
  • 目标插件必须已注册且启用(status=1),否则代理返回 404/403,与网络无关。
  • 代理会剥离你的 Authorization、Cookie、x-api-key、x-auth-token,所以对方后端不可能代你操作平台账号。

④ 完整示例(服务端调用可公开的 remote 插件)

// 服务端执行。API Key 必须同时具备对应权限码,不能只写 scope 就假设能调。
const resp = await fetch(
  'https://cfapi.aog.cc/api/plugins/<remote_code>/proxy/list',
  {
    method: 'GET',                    // 读接口;写接口改成 POST 并确保有 plugin.manage
    headers: {
      'Authorization': 'Bearer ' + process.env.CFAPI_API_KEY,
      'Accept': 'application/json'
    }
  }
);
if (!resp.ok) throw new Error('HTTP ' + resp.status + ': ' + await resp.text());
const result = await resp.json();
// 平台统一响应 { code, message, data };目标插件自定义业务结果在代理响应体中
if (result.code !== 0 && result.code != null) throw new Error(result.message || '插件调用失败');

⑤ remote 后端如何验证平台签名(必须实现)

平台转发时注入以下头。新接入只应验证按插件派生的 X-Platform-Signature,不要依赖旧的全局签名头:

请求头含义
X-Platform-Signature推荐校验:HMAC-SHA256(插件专属密钥, {plugin_code}{timestamp}{tenant})
X-Platform-Caller-Signature推荐校验:HMAC-SHA256(插件专属密钥, {plugin_code}{timestamp}{tenant}{sub})
X-Platform-Signature-Version当前为 v1,用于未来平滑升级
X-Platform-Timestamp秒级时间戳;建议拒绝 ±300 秒之外的请求
X-Platform-Plugin目标插件 code,必须与自身一致
X-Platform-Tenant调用者租户 ID,供多租户隔离
X-Platform-Caller-Sub调用者用户 ID(纯数字),用于用户级数据归属
X-Platform-Token / Caller-Token旧兼容头:使用平台全局密钥签名,外部插件无法验证也不应依赖;新代码改用 Signature 头
插件专属验签密钥从哪来?
① 市场已批准插件:原始提交者用登录 JWT 调 GET /api/market/submissions/{code}/signing-secret;
② 私有插件:归属人用具备 plugin.manage 的令牌调 GET /api/plugins/{code}/signing-secret。
两者都返回 按 plugin_code 派生的密钥,而不是平台全局密钥。它只能验证你这一个插件;放入服务端环境变量,禁止写前端或提交到仓库。平台轮换全局密钥后派生密钥会变化,需同步更新。

平台签名校验参考代码(必须实现)

常量时间比较 + 时间戳窗口 + plugin code 一致性,三者缺一不可。

Node / Express

const crypto = require('crypto');

function safeEqual(a, b) {
  const x = Buffer.from(String(a || ''), 'utf8');
  const y = Buffer.from(String(b || ''), 'utf8');
  return x.length === y.length && crypto.timingSafeEqual(x, y);
}

function verifyPlatform(secret, pluginCode, headers) {
  const sig    = headers['x-platform-signature'];
  const ts     = headers['x-platform-timestamp'];
  const tenant = headers['x-platform-tenant'];
  const plugin = headers['x-platform-plugin'];
  if (!sig || !ts || tenant == null || plugin !== pluginCode) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - Number(ts)) > 300) return false;
  const expected = crypto.createHmac('sha256', secret)
    .update(pluginCode + ts + tenant)
    .digest('hex');
  return safeEqual(sig, expected);
}

// 服务端路由中:
// if (!verifyPlatform(process.env.PLUGIN_SIGNING_SECRET, 'my_remote_plugin', req.headers)) {
//   return res.status(403).json({ code: 40300, message: 'invalid platform signature' });
// }

Python(Flask / FastAPI)

import hmac, hashlib, time

def verify_platform(secret, plugin_code, headers):
    sig = headers.get('X-Platform-Signature')
    ts = headers.get('X-Platform-Timestamp')
    tenant = headers.get('X-Platform-Tenant')
    plugin = headers.get('X-Platform-Plugin')
    if not sig or not ts or tenant is None or plugin != plugin_code:
        return False
    if abs(int(time.time()) - int(ts)) > 300:
        return False
    msg = f'{plugin_code}{ts}{tenant}'.encode()
    expected = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
    return hmac.compare_digest(sig, expected)

⑤-b 调用者身份与用户级数据岛

平台同时注入 X-Platform-Caller-Sub 与 X-Platform-Caller-Signature,用于证明本次请求代表哪个终端用户:

Caller-Signature = HMAC-SHA256(
  plugin_signing_secret,
  plugin_code + timestamp + tenant + caller_sub
)

校验方式与平台签名相同,只是消息多了末尾的 caller_sub。只有签名有效时才信任该用户号。

import hmac, hashlib, time

def verify_caller(secret, plugin_code, headers):
    sub = headers.get('X-Platform-Caller-Sub')
    sig = headers.get('X-Platform-Caller-Signature')
    ts = headers.get('X-Platform-Timestamp')
    tenant = headers.get('X-Platform-Tenant')
    if not (sub and sig and ts and tenant is not None):
        return None
    if not sub.isdigit() or abs(int(time.time()) - int(ts)) > 300:
        return None
    msg = f'{plugin_code}{ts}{tenant}{sub}'.encode()
    expected = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
    return sub if hmac.compare_digest(sig, expected) else None

用户级数据岛

  • 每个平台用户的文档、KV、文件与对象各归各的数据岛,A 不能读、删或覆盖 B 的同名数据。
  • 插件后端代表终端用户回调平台时,才能用该用户的数据岛;不能借调用者凭据跨到其他插件。
  • 匿名公共收件箱落入具名岛 public,只有超管可显式读写;普通用户与外部密钥会被拒绝。

注意:平台代理绝不会把调用者本人的凭据(Authorization / Cookie / x-api-key)转发给你的后端。你的后端若需回调平台,应使用自己的服务端凭据,而不是借用用户令牌。

⑥ Python 版完整调用示例

import os, requests

resp = requests.get(
    'https://cfapi.aog.cc/api/plugins/<remote_code>/proxy/list',
    headers={'Authorization': 'Bearer ' + os.environ['CFAPI_API_KEY']},
    timeout=20,
)
resp.raise_for_status()          # 403 = 权限码不足;404 = 插件/路径不存在;504 = 对方超时
data = resp.json()
if data.get('code') not in (None, 0):
    raise RuntimeError(data.get('message', 'plugin error'))

注意:被调用的插件具体暴露了哪些路径、要什么入参, 由该插件作者定义,请以其文档为准(卡片有「查看文档」按钮时优先看它)。平台只保证调用通道与鉴权可用。

九、最小可运行示例

按账号层级选择示例。外部开发者请使用「安全 & 发布」里的市场流程;下面的私有安装/启用仅适用于有 plugin.manage 的高级内部账号。

A. 私有 remote 插件(仅 internal_advanced)

# 1. 注册(全部写接口都要带同一枚 Bearer 令牌)
curl -sS -X POST https://cfapi.aog.cc/api/plugins \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"code":"demo_remote","name":"演示","version":"1.0.0","type":"remote","base_url":"https://your-server.example.com"}'

# 2. 启用(不能漏 Authorization,否则 401)
curl -sS -X POST https://cfapi.aog.cc/api/plugins/demo_remote/enable \
  -H "Authorization: Bearer $TOKEN"

# 3. 读调用(调用者需 plugin.view)
curl -sS https://cfapi.aog.cc/api/plugins/demo_remote/proxy/health \
  -H "Authorization: Bearer $TOKEN"

# 4. 写调用(调用者需 plugin.manage)
curl -sS -X POST https://cfapi.aog.cc/api/plugins/demo_remote/proxy/items \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"name":"demo"}'

B. 私有 local 插件(仅 internal_advanced)

# 根目录(或唯一顶层目录)必须含 plugin.json,且 code 与 query 一致
my_local_plugin/
├── plugin.json   # {"code":"my_local_plugin","name":"演示","version":"1.0.0","type":"local"}
├── index.html    # 固定入口,不要改名
└── app.js

# 原始 ZIP 字节上传,不是 multipart
curl -sS -X POST "https://cfapi.aog.cc/api/plugins/upload?code=my_local_plugin" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/zip" \
  --data-binary @my_local_plugin.zip

# 静态页面(无需 Bearer):https://cfapi.aog.cc/plugins/my_local_plugin/

C. 外部开发者发布到市场(正式路径)

完整命令见下一章「安全 & 发布」。核心区别:先登录 → 市场 multipart 上传拿到 download_url → 验证码 → /api/market/submit → 等待审批。不需要 plugin.manage,也不会在本地直接运行。

接口文档

下方只加载唯一权威合同 /api/openapi.json。openapi_extra.json 已废弃并清空,不再参与合并。权限码标签是接口要求,不代表当前账号已具备。

加载中… OpenAPI 查看原始 JSON ↗

加载中…

交互式调试(Swagger UI)

Swagger 与上方列表读取同一份主规范。调试前先点 Authorize 填 Bearer 令牌;403 请检查权限码与账号层级。

安全 & 发布

本章是外部开发者最该逐行读的部分:市场投稿、审核、安装、升级、回滚是四件不同的事,任何一步都不会“顺便”替你完成下一步。

一、外部开发者发布到市场(完整可执行流程)

外部账号可以自助投稿;最终上架仍由管理员审核。投稿全程不需要 plugin.manage,也不需要私有安装。
  1. 登录,拿用户 JWT

    POST /api/auth/login(JSON:username、password);后续请求统一带 Authorization: Bearer <data.token>。没有登录态会 401。

  2. 打包并自检

    ZIP 根目录或唯一顶层目录下必须有 plugin.json;code 只允许小写字母/数字/下划线,1–64 位,必须与上传参数完全一致。入口固定为 index.html;plugin.toml 不会被识别。

  3. 上传包(multipart,不是原始字节)

    POST /api/market/upload?plugin_code={code},Content-Type: multipart/form-data,文件字段名必须是 file,上限 500 MB。成功返回 data.download_url,但此时还没有提交审核。

  4. 获取一次性验证码

    GET /api/market/captcha 返回 token 与算术题 question。答案只用于紧接着的一次提交,过期/复用会失败。

  5. 提交审核

    POST /api/market/submit,JSON 必填 plugin_code、name、download_url,再带 version(严格 x.y.z)、description、author、email、category、tags、captcha_token、captcha_answer。成功进入 pending。

  6. 查看状态 / 撤回

    GET /api/market/submissions?status=pending 只能看到自己的提交;POST /api/market/submissions/{code}/withdraw 仅能撤回自己处于 pending 的记录。撤回是删除动作:该提交记录会被移除,不保留草稿;已进入审核结果(approved / rejected)的提交不能撤回,要重新提交只能再走一次 POST /api/market/submit。

  7. 管理员审核

    POST /api/market/submissions/{id}/approve 需 plugin.govern;静态扫描命中危险项时默认拒绝,只有显式 force=1 才能强制批准。

  8. 批准后发生了什么

    只更新市场提交状态并新增/切换当前版本记录。不会安装到任何人的插件目录、不会自动启用、不会自动创建或启动沙箱。安装由具备 plugin.manage 的实例管理员发起,不是任意终端用户能自助完成的动作。

  9. remote:取插件专属验签密钥

    批准后,原始提交者用登录 JWT 调 GET /api/market/submissions/{code}/signing-secret,把返回的 plugin_secret 放服务端环境变量,用于验证 X-Platform-Signature / X-Platform-Caller-Signature。该密钥按插件派生,不是平台全局密钥;禁止写进前端或仓库。

可直接复制的最小命令链

API=https://cfapi.aog.cc
# 1) 登录(把用户名、密码换成你的)
TOKEN=$(curl -sS -X POST "$API/api/auth/login" \
  -H 'Content-Type: application/json' \
  -d '{"username":"YOUR_USER","password":"YOUR_PASSWORD"}' \
  | python3 -c 'import sys,json; print(json.load(sys.stdin)["data"]["token"])')

# 2) 市场上传:multipart + 字段名 file
curl -sS -X POST "$API/api/market/upload?plugin_code=my_plugin" \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@my_plugin.zip;type=application/zip"
# → {"code":0,...,"data":{"download_url":"/api/market/uploads/my_plugin.zip",...}}

# 3) 取验证码(返回 token + question,人工/程序算出答案)
curl -sS "$API/api/market/captcha" -H "Authorization: Bearer $TOKEN"

# 4) 提交审核(答案填上一步)
curl -sS -X POST "$API/api/market/submit" \
  -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
  -d '{"plugin_code":"my_plugin","name":"我的插件","version":"1.0.0",
       "download_url":"/api/market/uploads/my_plugin.zip",
       "description":"功能说明","category":"productivity",
       "captcha_token":"<token>","captcha_answer":"<answer>"}'

# 5) 查看我的提交
curl -sS "$API/api/market/submissions" -H "Authorization: Bearer $TOKEN"
常见失败原因:上传用了 JSON/raw 而不是 multipart;文件字段不叫 file;plugin_code 带连字符或与 plugin.json 不一致;版本不是三段数字;版本不高于该插件历史最高上架版本;验证码被复用;重复使用别人已占用的插件标识。

二、升级、回滚、下架(语义不要猜)

操作接口实际效果
发布新版本再次走 upload + submit版本必须严格高于历史最高上架版本;重新进入 pending,再次审核
查看版本历史GET /api/market/versions/{plugin_code}仅提交者本人或超管可看
切换当前版本POST /api/market/versions/{id}/set-current需 plugin.govern。只切换市场“当前版本”指针与目录展示;不会替换已部署文件、不会重装、不会重启 remote 服务
下架POST /api/market/plugins/{code}/unpublish需 plugin.govern + 归属校验;市场目录与安装入口同时移除
管理员更新已安装插件再次 POST /api/plugins/market/install需 plugin.manage;local 才会重新拉包,remote 只更新登记
因此“回滚插件”不能只点 set-current。对已经安装的 local 插件,要重新安装目标版本;对 remote 服务,发布与回滚由你自己服务器完成。平台无“一键替换运行中第三方服务”的能力。

三、local / remote 的真实安全边界

形态平台提供平台不提供
local同源静态托管、路径越界防护、HTML 禁缓存、其余资源 7 天缓存、符号链接清除、ZIP 静态扫描服务端代码隔离、密钥保管、浏览器端权限控制。页面里的任何密钥都可被访客读取
remote鉴权反向代理、按插件隔离的签名密钥与签名头、剥离用户凭据、SSRF 校验、响应 16 MB 上限、默认 15 秒超时你的服务器补丁、数据备份、进程隔离与可用性。你的后端必须自行校验 X-Platform-Signature / X-Platform-Caller-Signature
wasm/sandbox受控执行接口、配置化网络/文件/资源限制、运行记录“注册类型即自动沙箱”;外部开发者自助运行;无限制的系统调用能力

静态扫描只能降低明显风险,不等于代码审计。审核通过也不意味着平台为插件行为背书;插件造成的业务、数据与合规责任仍由发布者承担。

四、沙箱真实配置与资源上限

沙箱必须在插件之外另有配置记录且生命周期为 active;运行时只接受 wasm / process,路径为 POST /api/plugins/{code}/sandbox/exec,需要 plugin.manage,且受内部层级策略约束。

项目默认硬限制 / 说明
执行超时5000 ms最小 500 ms,最大 120000 ms
内存256 MB最小 16 MB,最大 2048 MB
CPU1000 ms硬上限 120000 ms
并发8全平台同时运行沙箱数上限
单次日志落库64 KB超出截断,避免撑爆数据库
网络deny可在受控配置中改为 allow;不是所有执行都天然断网
文件系统readonly可选 allow/readonly/deny;隔离强度取决于宿主能力与实际策略
WASM 运行时wasmiwasmtime 仅在编译 feature 开启时可用;未开启会明确报错
入口文件—必须是相对路径,禁止绝对路径与目录穿越;文件缺失即拒绝

进程沙箱依赖 unshare/chroot/prlimit 等宿主能力;缺少必要隔离条件时会拒绝需要隔离的执行,而不是静默降级。

五、排错与上架前检查清单

现象先检查正确动作
401 缺少登录凭证 / 登录已过期是否漏了 Authorization;令牌是否复制完整重新登录;每个写请求都带上 Bearer,不要只给第一个请求加
403 缺少权限 / 层级不允许权限码、账号层级、目标插件层级市场投稿不需要 plugin.manage;私有管理才需要。不要用换密钥掩盖缺权限
上传返回“缺少 plugin.json”ZIP 是否只有一层目录;清单是否真的叫这个名字把清单放在根或唯一顶层目录;Windows 下确认没有多套一层根目录
上传返回“code 与上传路径不一致”plugin.json.code 与 query 参数两者改成完全相同的小写标识
市场提交返回版本错误是否是 x.y.z、是否高于历史最高版本递增 PATCH,不能原地覆盖同版本
代理 502 / 504base_url 是否可访问、是否被 SSRF 规则拒绝、对方是否超时先用服务端 curl 自测;确认公网可达、HTTPS、响应在 16 MB 内
批准后市场仍不能安装插件是否被下架、用户是否具备安装权限审核批准 ≠ 自动安装;安装是独立动作
上架前逐项确认:清单 code 一致;入口 index.html 可直接打开;无密钥、无私钥、无真实用户数据;所有请求有超时与错误分支;remote 服务校验平台签名;版本已递增;验证码与下载地址来自本次上传;README 已按真实端点编写。

插件示例下载

local-h5 模板 my_local_plugin

下载 zip local · 静态托管

local v1.0.0

remote-node 模板 my_remote_plugin

下载 zip

remote v1.0.0

旧 CDN 图片库 / 字体库模板已从推荐示例中移除。它们把高权限令牌保存在浏览器 localStorage,且 README 描述的接口路径与当前主规范不一致,继续作为“官方范式”会误导开发者。历史文件不再从本页链接;请以本页 local / remote 两个模板为安全基线。
模板的正确用法:解压修改 → 保证 plugin.json.code 与你要上传的 plugin_code 一致 → 市场投稿(multipart)或内部高级账号私有安装(raw ZIP)。
不要把模板当“批准后就会自动运行”的示例;市场批准只完成上架,安装与启用是后续动作。

可调用插件

这里列出已上架且启用的插件元数据。只有 remote 且配置了 base_url 的插件可以经 /api/plugins/{code}/proxy/ 调用;local 插件没有可代理后端。

“看得到”不等于“调得动”。读调用需要 plugin.view,写调用需要 plugin.manage。仅有 external 身份不会自动获得权限码;API Key 的 scope 也不是完整授权。若下方试调返回 403,请与平台管理员确认权限码,而不是反复更换密钥。

我的工作台

先按层级选工具。 外部开发者用「市场投稿」,不需要 plugin.manage,也不会在本站直接运行插件;高级内部开发者才会看到并使用「私有插件」。

① 登录态 / 开发令牌

从用户中心已登录状态自动同步;也可以手动粘贴。令牌只保存在本页内存,不上传第三方。市场投稿允许普通登录身份,私有插件管理才需要相应权限。

② 私有插件(仅 internal_advanced)

这一组操作需要 plugin.manage 且只能管理归属于自己的插件。普通外部与普通内部账号无法使用;外部开发者请直接看下方「市场投稿」。
创建 remote 私有插件
上传 local 私有插件(ZIP 原始字节)

③ 市场投稿(所有已登录开发者)

上传得到 download_url → 获取验证码 → 提交审核。批准后进入市场;由具备 plugin.manage 的实例管理员安装是另一个动作。

④ 我的市场提交

点上方按钮查看。

我拥有的私有插件

点「刷新状态」加载。

内部能力(仅内部开发者)

本区块是层级说明,不是权限授予开关。界面显隐只影响观感,真正的拦截在服务端。你的开发者层级由后台管理员设置,共三档: external(默认,无自动权限)、internal(仅 plugin.view)、 internal_advanced(plugin.view、plugin.manage、dev.manage)。层级变更最迟约 20 秒生效。
两档内部账号的差别:普通内部只读,能查看与调用具备 view 的接口,不能创建、上传、启用插件,也不能执行沙箱。高级内部才具备插件写能力与受控沙箱执行能力,但仍只能管理归属于自己的插件;平台插件与别人的插件不改归属就不能覆盖、启停或删除。

治理权不等于“内部高级自带”:审批、下架、切换当前版本、scope 授权等需要 plugin.govern;该权限由后台 RBAC/超管持有,并仍有插件归属校验。任何开发者层级都不会自动获得它。

内部专属能力组(是否可用由层级策略与权限码共同决定)

kernel
事件、Hook、UI Slot、配置与内核扩展
sandbox
WASM / 进程受控执行
scheduler
定时任务
files
插件文件与受控文件接口
jobqueue
异步任务队列

如何获得内部文档与能力

内部版铁律与使用说明书不在公开静态目录提供,必须用有效内部凭证从受鉴权入口读取;开发者中心的「平台文档」会在检测到内部层级后通过带 Authorization 的请求加载,不把内部副本暴露成公开 URL。

若需要更高层级,请由后台管理员在「用户管理」调整;不要尝试用令牌文本或前端显隐伪造层级。外部账号即使修改页面元素,服务端仍按真实账号层级判定并返回 403。

内部层级不改变插件归属:普通内部只读;高级内部可管理自己的插件;平台插件仅由具备治理权的管理员维护。越权调用会写入审计。

高级内部开发者:能力边界

  • 可创建、上传、启停、卸载归属于自己的插件。
  • 可在已有 active 沙箱配置且层级策略允许时执行沙箱;默认网络 deny、文件系统 readonly。
  • 可写入自身插件的配置、数据与受控文件能力。
  • 不可管理平台插件或他人插件;不可审批自己的市场投稿;不可授予 scope 或修改层级策略。

不要把“高级内部”理解成“小型超管”。生产变更会进入审计;治理与归属校验不会因层级高而跳过。

常见问题

Q:我的前端在自己域名,调接口报跨域?

把域名加入后台 cors_allowed,或改用 remote 插件 + /proxy/ 反向代理。

Q:插件数据存哪?能直连数据库吗?

数据通过 REST 接口(文档集合 / 键值 / 对象存储)读写,禁止直连数据库(对插件是黑盒)。

Q:平台自己用什么数据库?能换吗?

三种后端:SQLite 3.46(默认,库文件 app/var/data/cfapi.db)、MariaDB 11.8、PostgreSQL 18.6。后台「数据库管理」可一键互斥切换,或命令行 scripts/db_port/cutover.sh sqlite|mysql|postgres;切换自动完成「停写 → 快照 → 搬运 → 行级校验(差异必须为 0)→ 重启 → 健康检查」,任一步失败自动回滚。

Q:换数据库会影响插件吗?

不会。插件看不到后端类型,也不允许直连;切换期间会短暂停写,避开高峰即可。当前后端可查 GET /api/db-backend/status(需管理凭证)。

Q:更新了插件前端,用户看不到新版?

静态资源有 7 天缓存,让用户强刷(Ctrl+Shift+R)或给资源加版本号参数。

Q:想做付费功能?

用 /api/plugins/{code}/payments 发起扣费,无需自己接支付通道。

Q:想接 AI?

用 /api/plugins/{code}/ai/chat,平台网关已对接多家大模型。

Q:高并发被限流?

先按 429/42900 做退避重试,减少并发与重复请求。额度由平台管理员在后台调整,外部开发者不能在客户端自行扩容;换密钥不会绕过限流。

开发工具

点击标签在下方内置窗口打开;刷新与新窗口按钮在右侧。

SDK 与接口集合

由公开契约自动生成,覆盖 138 个公开/插件操作;内部管理接口不在其中,也不承诺兼容。生成源见 openapi.public.json。

SDK 用法:client.setConfig({ baseUrl: 'https://cfapi.aog.cc' }),登录后把 data.token 放进 Authorization: Bearer。Postman 集合已内置 baseUrl 与 token 两个变量。

接入协议与选型指引

平台对外提供 20 类接入方式。先看「怎么选」,再看每种的实际限制,避免选错协议白做工。以下能力均已在线验证。

HTTP REST ConnectRPC gRPC JSON-RPC GraphQL WebSocket SSE Webhook MCP CLI OpenAPI OAuth2 JWT APIKey AdminAPI MetricsAPI HealthCheck OIDC SAML2

其中 gRPC 仅提供 gRPC-Web(JSON) 兼容形态,原生二进制 gRPC 不支持,详见下表。

一、四句话选型

你要做的事用什么
插件内部调用平台能力REST + 插件作用域令牌;事件回调用 Webhook
自建后端 / 外部系统集成API Key 或 OAuth2 / OIDC,配合 OpenAPI 生成 SDK
AI / Agent 工具接入MCP(首选)或 JSON-RPC 2.0
需要强类型 RPC 客户端Connect(官方 Connect 客户端原生支持)
原生 gRPC 未提供。平台提供的是 gRPC-Web(JSON) 兼容形态:请求体是 JSON,不是二进制 gRPC 帧,也不返回 trailers。官方 gRPC 客户端无法直连,请改用 Connect。

二、逐项能力与限制

协议入口适用场景限制 / 注意
HTTP / REST/api/plugins/{code}/...插件开发主通道:数据、KV、存储、文件、配置、状态、AI、支付统一响应 {code,message,data},code=0 为成功
JSON-RPC 2.0POST /api/rpc轻量脚本、批量调用8 个方法:health / version / openapi / plugins.list / plugins.get / market.list / db.tables / http.request;其中 http.request 可转发任意 REST 端点(万能兜底),支持批量数组
MCPPOST /api/mcpAI / Agent 工具发现与调用8 个工具;标准流程 initialize → tools/list → tools/call
ConnectRPC(Connect)POST /connect/cfapi.v1.{Service}/{Method}强类型 RPC3 个服务 5 个方法:SystemService.Health / Version / Watch,PluginService.List / Get,GatewayService.Call;支持 json 与 proto 两种 codec,Watch 为服务端流
gRPC(原生)——不支持:无二进制帧与 trailers,请用 Connect 或下面的兼容形态
gRPC-Web(JSON)POST /api/grpc/{service}/{method}调试兼容JSON 封装而非二进制;方法名用点分形式,如 /api/grpc/system/health
GraphQLPOST /api/graphql一次请求取多类数据12 个字段(me / plugins / users / roles / 角色增删改 / 插件注册 / 上下架 / 市场安装 / 超管改状态),偏管理面
WebSocketGET /ws?token=<JWT>实时系统事件必须 JWT;订阅全站事件仅限内部层级,外部令牌会被拒
SSEGET /api/events轻量事件流只推 system_event 与 error,不是插件业务推送通道
Webhook/api/plugins/{code}/hooks插件事件回调首选平台按事件投递到你的 URL,带投递记录、重试与测试触发
OpenAPIGET /api/openapi.json生成各语言 SDK、导入 PostmanOpenAPI 3.1.0
OAuth2POST /api/oauth/token终端用户授权码流程面向你的应用用户,不是插件间互调
OIDCGET /api/oauth/.well-known/openid-configuration标准发现 + JWKS可据此自行验签用户令牌,无需回源
JWTAuthorization: Bearer <jwt>用户身份、离线验签EdDSA(Ed25519);公钥可从 JWKS 获取
API Key(APIKey)Authorization: Bearer <key>服务端到服务端调用分内部 / 外部两级;外部默认最小权限(见第四节)
AdminAPI/api/admin/*平台管理需管理员权限,不是插件接口
Metrics(MetricsAPI)GET /metricsPrometheus 抓取运维面,插件不应依赖
HealthCheckGET /api/health探活运维面
CLI平台内 scripts/cfapi-cli.sh运维脚本不是插件开发接口

三、三条身份通道(别混用)

通道怎么拿用在哪
插件作用域令牌POST /api/plugins/{code}/token(用插件专属验签密钥与 X-Platform-* 头换取),POST /api/plugins/{code}/token/verify 校验插件后端调平台:数据 / KV / 存储 / 文件 / 状态 / AI;scope 必须先由管理员授权
API Key后台创建,内部 / 外部两级你的服务器、脚本、CI 调平台 REST
用户 JWT / OAuth2 + OIDC用户登录后签发;或走 OAuth2 授权码代表终端用户操作、前端直连

三者权限范围不同:插件令牌只在所属插件的命名空间内有效;API Key 按层级与授权范围收敛;用户 JWT 只能做该用户被允许的事。

四、外部开发者的默认权限边界

能力分组外部层级默认说明
datastore / jobqueue / storage / proxy / cdn开放数据、任务队列、对象存储、反向代理、图片/字体库
kernel / sandbox / scheduler / files / shell关闭内核扩展、WASM 沙箱、调度器、文件系统、命令执行;需管理员在后台显式授权
越权调用会返回 403 并写入审计日志。这不是偶发故障,是默认拒绝式的最小权限设计;需要开通就在后台「接口层级权限」里勾选。

五、插件作用域端点清单

能力端点(前缀 /api/plugins/{code})
文档数据/data/{collection}、/data/{collection}/{doc_id}、/data/{collection}/schema、/schemas
键值(带 TTL)/kv/{key}
对象存储/storage/objects、/storage/objects/{key}
文件/files、/files/upload、/files/download/{path}
配置与状态/config、/config/schema、/store、/store/{key}
插件间 RPC/rpc、/grants、/grants/{scope}
界面插槽/ui-slots、/ui-slots/{id}
事件回调/hooks、/hooks/{id}
访问令牌/token、/token/verify
AI/ai/chat、/ai/usage
支付/payments
沙箱执行/sandbox/exec(需该分组被授权)
健康/health、/health/ping

六、最小可用示例

# 1) 插件后端读自己的数据(插件作用域令牌)
curl -H "Authorization: Bearer $PLUGIN_TOKEN" \
  "https://cfapi.aog.cc/api/plugins/{code}/data/{collection}?page=1&page_size=20"

# 2) 外部系统用 API Key 调 REST
curl -H "Authorization: Bearer $API_KEY" \
  "https://cfapi.aog.cc/api/plugins"

# 3) MCP:先发现工具,再调用
curl -X POST https://cfapi.aog.cc/api/mcp \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

# 4) Connect(官方客户端等价于这个 POST)
curl -X POST https://cfapi.aog.cc/connect/cfapi.v1.SystemService/Version \
  -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{}'

完整接口清单可在「接口文档」页按关键词搜索;需要 SDK 就直接用 OpenAPI 规范生成。

AI 开发资料

给编码助手一套确定的事实源,避免它从页面文案里猜能力。建议按「索引 → 完整上下文 → API 合同 → 清单 Schema」顺序提供。

AI 索引 先读

硬约束、两条路线、禁用事项与推荐提示词。

打开 llms.txt ↗

完整上下文

市场流程、权限、代理、沙箱、常见错误与可复制代码。

打开 llms-full.txt ↗

结构化上下文

供程序或 Agent 消费的 JSON:权限、端点、工作流与边界。

打开 ai-context.json ↗

清单 Schema

校验 plugin.json,避免 code、type、entry 写错。

打开 JSON Schema ↗

唯一 API 合同

OpenAPI 3.1,与 /api/openapi.json 同一份内容。

打开 OpenAPI ↗

文档变更记录

本次修正了哪些错误、删除了哪些不实能力。

打开 CHANGELOG ↗

可直接复制的开发提示词

纯文本版 ↗

机器合同的诚实边界

  • OpenAPI 覆盖已挂载路由;核心插件端点和市场流程已给出请求体。
  • 少数内部运维接口仍可能只有泛化请求体,AI 不应靠猜;先发最小请求并读错误。
  • openapi_extra.json 已废弃并清空,不要再作为补充接口来源。
  • “字段存在”不等于“当前账号有权限”。权限码、层级、插件归属、启用状态都要同时判断。

平台文档

开发铁律 v0.5.17。公开版直接显示;内部版经鉴权接口读取。插件接口以 OpenAPI 与 AI 开发资料为准。

内部版需内部凭证;平台运维说明书不在开发者中心展示,避免过期运维内容误导插件开发。