SStar Developers
可信后端发布站点价格查看 Markdown

站点自主定价 API#

目标:平台一次授权后,站点在自己的管理后台或可信部署脚本中调价。无需反复让平台改代码、改数据库或人工发布;Star Task 继续统一报价与扣点。

1. 平台一次授权#

在 Star Admin → 站点管理 → 定价授权 签发凭证:

  • pricing:read:读取当前规则、试算草稿、读取本站历史。
  • pricing:write:发布、恢复继承、回滚。写入凭证同时包含读取权限。
  • min_points:允许发布的最低正整数点数,1–1000000。图片按每张,其他固定价格按每次。它是平台设定的授权下限,不是自动计算的上游成本;应依据站点结算约定设置。
  • 有效期:1–365 天,默认 90 天。到期前签发新凭证,站点替换 Secret 后撤销旧凭证。

凭证只在创建响应显示一次;数据库仅保存 SHA-256 摘要。放在站点 Worker/后端 Secret SITE_PRICING_TOKEN,不得进入浏览器、公开环境变量、源码或日志。总后台可随时撤销;撤销不撤销已发布价格。停用站点也会拒绝该站点的凭证。

平台自动化也可用管理员会话调用 Star Admin 主机:

http
POST /admin/sites/<SITE_ID>/pricing-tokens
Authorization: Bearer <STAR_ADMIN_SESSION_TOKEN>
Content-Type: application/json

{"name":"production-backend","scopes":["pricing:read","pricing:write"],"min_points":1,"expires_in_days":90}

返回 id、site_id、token、scopes、min_points、expires_at。时间均为 Unix 毫秒。列表:GET /admin/sites/<SITE_ID>/pricing-tokens;撤销:POST /admin/sites/<SITE_ID>/pricing-tokens/<TOKEN_ID>/revoke。平台可信服务也可使用 Star Task /internal/admin/sites/.../pricing-tokens 同名接口和 TASK_ADMIN_SECRET。这些管理凭证不能交给站点。

2. 站点调用约定#

基地址:https://task.starhk.uk/site-api/v1/pricing。

所有请求使用 Authorization: Bearer <SITE_PRICING_TOKEN>。该接口独立于普通用户 JWT、普通站点 API Key 与全局管理 API;这些凭证不能替代定价凭证。不传 site_id 或 X-Site-Id,身份由凭证确定。浏览器请求(带 Origin)会被拒绝;由站点后端调用。

站点自己的管理入口必须先验证站点管理员身份并实施 CSRF 防护;不得把持有写入凭证的代理接口开放给普通登录用户或匿名访问者。后端固定 Star Task 基地址,不接受客户端传入任意 URL。

先通过公开运行时目录 GET /v1/models?view=products&site_id=<SITE_ID> 选择精确的 modes[].task_model、task_type 和参数。不能把产品显示名猜成模型 ID;不同生成模式对应不同模型 ID 时分别发布。

方法与相对路径权限用途
GET /accessread本凭证的站点、权限、下限、到期时间
GET /models/<TASK_MODEL>read当前有效规则、来源及 etag
POST /models/<TASK_MODEL>/previewread校验草稿并报价,不写入
PUT /models/<TASK_MODEL>write发布或恢复继承
GET /models/<TASK_MODEL>/historyread最新 50 条本站自主发布记录
POST /models/<TASK_MODEL>/rollbackwrite将指定历史版本重新发布

响应为顶层 { "ok": true, ... },不是 data 包裹。错误为 { "ok": false, "error": { "code", "message" } }。响应不缓存;JSON 请求上限 32 KiB。

3. 简化价格定义#

图片分辨率价格:

json
{
  "type": "image_resolution",
  "field": "resolution",
  "prices": { "1K": 3, "2K": 4, "4K": 6 }
}

field 用运行时表单实际字段,允许 resolution 或 quality。档位键使用大写 K,可表达 0.5K、1K、2K、4K、8K;只配置模型实际支持的档位,并覆盖所有可用档位。未匹配的输入按已配置的最高档点数回退;发布前应逐档预览,不应依赖这个回退代替正确参数。

固定价格:{"type":"fixed","points":3}。恢复继承:{"type":"inherit"}。

所有价格必须是授权下限至 1000000 之间的整数。图片按每张计费,规则会乘请求中的 n(默认 1),例如 2K、n=2 为 8 点;其他媒体固定价按每次任务计费。使用其他输出数量字段、文本 Token 计费、按秒/质量复合规则或供应商自行决定扣点的模型,不应套用这个简化接口,应保留平台维护的规则。

每次发布替换该站点该模型的完整规则,不合并旧附加费、质量倍率或其他字段。调用者不能提交 rules_json、任意公式、task_type 或其他站点身份。模型任务类型由服务端解析。

4. 预览与发布示例(仅服务端)#

下面函数应在已经验证站点管理员身份的后端操作或可信 CLI 中运行。taskModel 来自运行时目录,token 来自服务端 Secret。不要自动覆盖冲突。

ts
export async function publishImagePrice(token: string, taskModel: string) {
  const base = 'https://task.starhk.uk/site-api/v1/pricing';
  const modelPath = '/models/' + encodeURIComponent(taskModel);
  async function call(path: string, method = 'GET', body?: unknown) {
    const response = await fetch(base + path, {
      method,
      headers: {
        Authorization: `Bearer ${token}`,
        ...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
      },
      ...(body === undefined ? {} : { body: JSON.stringify(body) }),
    });
    const result = await response.json() as any;
    if (!response.ok) {
      if (response.status === 409) throw new Error('价格已被修改,请重新读取并让管理员确认');
      throw new Error(result.error?.message ?? `HTTP ${response.status}`);
    }
    return result;
  }
  const current = await call(modelPath);
  const pricing = {
    type: 'image_resolution', field: 'resolution',
    prices: { '1K': 3, '2K': 4, '4K': 6 },
  };
  const preview = await call(modelPath + '/preview', 'POST', {
    pricing,
    inputs: [
      { resolution: '1K', n: 1 },
      { resolution: '2K', n: 1 },
      { resolution: '4K', n: 1 },
      { resolution: '2K', n: 2 },
    ],
  });
  // 在站点管理页面将 preview.quotes 展示给管理员。
  const expected = [3, 4, 6, 8];
  if (preview.quotes.some((q: any, i: number) => q.cost_points !== expected[i])) {
    throw new Error('试算结果与期望不一致');
  }
  return call(modelPath, 'PUT', { pricing, expected_etag: current.etag });
}

交互式后台可将读取/预览和发布拆成两个操作;保存管理员审阅时的 etag,不要在点击发布后静默获取新值覆盖它。preview 接受 1–20 个参数对象,返回同计价引擎算出的 quotes[].cost_points 和 breakdown。

PUT 成功返回 revision_id、新 etag、source_site_id、pricing_version、rules_json。未设置本站价格时,恢复继承返回 unchanged:true,不新增审计。

5. 冲突、恢复、回滚#

读取响应的 etag 是本站该模型当前状态标识,不是公共目录的最大 pricing_version。每次写入必须传入 expected_etag;过期或遗漏返回 409 PRICING_CONFLICT。更新规则和写入审计在同一事务中完成;并发修改只有匹配原状态的一次成功。

恢复全局价格:

json
{"expected_etag":"<current etag>","pricing":{"type":"inherit"}}

回滚:先读取历史,再 POST /models/<TASK_MODEL>/rollback:

json
{"expected_etag":"<current etag>","revision_id":"<revision to restore>"}

回滚把目标记录的已发布配置重新发布,生成新审计记录,仍检查当前凭证的最低点数。只能回滚本站、同模型的自主发布历史;不支持直接回滚总后台或旧 SQL 修改。历史包含凭证 ID(可追溯签发名称)、修改前后规则和时间,不包含凭证明文。站点如多人共用一个凭证,平台只能识别到该凭证,站点需自行记录操作者。

请求超时或连接中断时,先读取当前状态与历史确认是否已成功,再决定是否重新发布;不要盲目自动重试写入。401 表示凭证无效/过期/撤销,403 表示权限不足、站点停用或浏览器调用,400 表示参数或最低价校验失败,404 表示模型不存在、停用或未获本站模型权限。

6. 生效与验收#

发布后无需重新部署前端。前端应主动失效自己的模型/规则缓存,重新获取本站目录,并调用正常的 POST /v1/pricing/quote。公开目录可能有短时 HTTP 缓存,不要只依赖全局最大版本号判断本站修改。创建任务响应的 cost_points 是实际扣点依据;已创建任务保留原扣点。

验收:1K/2K/4K 报价为 3/4/6;2K 两张为 8;其他站点不变;恢复继承后使用全局价;只读/撤销/过期凭证不能写入;冲突返回 409;浏览器产物不含定价凭证。

7. 平台上线顺序#

  1. 在 Star Task 的 task-db 执行迁移 0086_site_pricing_access.sql(npm run db:migrate:remote:086)。此迁移只创建凭证与审计表,不修改现有价格。
  2. 部署 Star Task(新 API 和文档),再部署 Star Auth(授权入口)。
  3. 为需要自主定价的站点签发最小权限凭证,交由其服务端保存。
  4. 通过预览和正常报价验收后,站点自行发布价格。

升级不会自动给已有站点增加写权限,也不会自动发布示例中的 3/4/6 价格。旧管理接口和已有前端报价/扣点流程继续可用。

Opt-in image resolution contract v1#

The existing image_resolution request accepts optional contract_version: 1, default_resolution: "1K", and unit: "output" | "request". Version 1 requires complete supported resolution coverage, normalizes aliases and rejects unmatched tiers; omitting the version preserves historical behavior. See image resolution pricing for full examples and frontend quote protection.