站点自主定价 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 主机:
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 /access | read | 本凭证的站点、权限、下限、到期时间 |
GET /models/<TASK_MODEL> | read | 当前有效规则、来源及 etag |
POST /models/<TASK_MODEL>/preview | read | 校验草稿并报价,不写入 |
PUT /models/<TASK_MODEL> | write | 发布或恢复继承 |
GET /models/<TASK_MODEL>/history | read | 最新 50 条本站自主发布记录 |
POST /models/<TASK_MODEL>/rollback | write | 将指定历史版本重新发布 |
响应为顶层 { "ok": true, ... },不是 data 包裹。错误为 { "ok": false, "error": { "code", "message" } }。响应不缓存;JSON 请求上限 32 KiB。
3. 简化价格定义#
图片分辨率价格:
{
"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。不要自动覆盖冲突。
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。更新规则和写入审计在同一事务中完成;并发修改只有匹配原状态的一次成功。
恢复全局价格:
{"expected_etag":"<current etag>","pricing":{"type":"inherit"}}回滚:先读取历史,再 POST /models/<TASK_MODEL>/rollback:
{"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. 平台上线顺序#
- 在 Star Task 的 task-db 执行迁移
0086_site_pricing_access.sql(npm run db:migrate:remote:086)。此迁移只创建凭证与审计表,不修改现有价格。 - 部署 Star Task(新 API 和文档),再部署 Star Auth(授权入口)。
- 为需要自主定价的站点签发最小权限凭证,交由其服务端保存。
- 通过预览和正常报价验收后,站点自行发布价格。
升级不会自动给已有站点增加写权限,也不会自动发布示例中的 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.