Goal
用应用后端创建 Free/Pro Plan,并把两个稳定的应用用户 ID 分配到对应 Access。
Outcome
每名用户得到独立的 Better Auth User、WorkspaceAccess、当前 QuotaPeriod 和一次性 Inference Key。最终用户不是 Organization Member,也不获得 Admin 权限。
Prerequisites
- 一个 Workspace ID 和可写该 Workspace 的 Management Key。
- Node.js 22.12+。
- 生产环境中的安全凭证交付通道。
Lifecycle note
WorkspacePlan 是稳定套餐;发布后的 QuotaStrategy 通过 Binding 生效。Provision 以 (workspaceId, externalUserId) 标识应用用户。Key、Access 和 Period 是三个独立生命周期。
1. Configure the backend
export NEXUS_ADMIN_BASE_URL="https://nexus.microvoid.io"
export NEXUS_MANAGEMENT_KEY="<NEXUS_MANAGEMENT_KEY>"
export NEXUS_WORKSPACE_ID="<WORKSPACE_ID>"2. Run the complete provisioning program
import { writeFile } from 'node:fs/promises';
const baseUrl = required('NEXUS_ADMIN_BASE_URL').replace(/\/$/, '');
const managementKey = required('NEXUS_MANAGEMENT_KEY');
const workspaceId = required('NEXUS_WORKSPACE_ID');
async function admin(path, { method = 'GET', body } = {}) {
const response = await fetch(`${baseUrl}${path}`, {
method,
headers: {
Authorization: `Bearer ${managementKey}`,
...(body === undefined ? {} : { 'Content-Type': 'application/json' }),
},
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
if (!response.ok) throw new Error(`${method} ${path} failed: ${response.status} ${await response.text()}`);
return response.status === 204 ? undefined : response.json();
}
async function createActivePlan(definition) {
const plan = await admin(`/admin/v1/workspaces/${workspaceId}/plans`, {
method: 'POST',
body: { key: definition.key, name: definition.name, billingInterval: 'MONTH' },
});
const strategy = await admin(`/admin/v1/workspaces/${workspaceId}/plan-strategies/quota`, {
method: 'POST',
body: {
key: `${definition.key}-quota`,
resetInterval: 'MONTH',
quotaUnits: definition.quotaUnits,
inputMultiplierPpm: '1000000',
outputMultiplierPpm: '2000000',
cachedInputMultiplierPpm: '200000',
maxOutputTokens: definition.maxOutputTokens,
},
});
await admin(`/admin/v1/plan-strategies/${strategy.id}/publish`, { method: 'POST' });
await admin(`/admin/v1/plans/${plan.id}/strategy-bindings`, {
method: 'POST',
body: { strategyId: strategy.id, effectiveFrom: new Date().toISOString() },
});
return admin(`/admin/v1/plans/${plan.id}/publish`, { method: 'POST' });
}
async function provision(planId, user) {
return admin(`/admin/v1/workspaces/${workspaceId}/users`, {
method: 'POST',
body: { ...user, planId, issueInferenceKey: true },
});
}
const free = await createActivePlan({ key: 'free', name: 'Free', quotaUnits: '1000000', maxOutputTokens: 2048 });
const pro = await createActivePlan({ key: 'pro', name: 'Pro', quotaUnits: '50000000', maxOutputTokens: 8192 });
const freeUser = await provision(free.id, {
externalUserId: 'app-user-free-001',
email: 'free-user@example.com',
name: 'Free example user',
});
const proUser = await provision(pro.id, {
externalUserId: 'app-user-pro-001',
email: 'pro-user@example.com',
name: 'Pro example user',
});
await writeFile(
'provisioned-inference-keys.json',
JSON.stringify(
{
free: { accessId: freeUser.workspaceAccess.id, apiKey: freeUser.apiKey },
pro: { accessId: proUser.workspaceAccess.id, apiKey: proUser.apiKey },
},
null,
2,
),
{ mode: 0o600, flag: 'wx' },
);
console.log(JSON.stringify({ freePlanId: free.id, proPlanId: pro.id, keyFile: 'provisioned-inference-keys.json' }));
function required(name) {
const value = process.env[name]?.trim();
if (!value) throw new Error(`${name} is required`);
return value;
}node provision-saas-users.mjs示例额度用于说明流程,不是产品定价建议;Quota Unit 表达 AI 使用权,不是钱。
Check your work
- 两个 Plan 为
ACTIVE,各绑定一个已发布 QuotaStrategy。 - 两条 WorkspaceAccess 的
externalUserId、planId和当前 QuotaPeriod 正确。 provisioned-inference-keys.json权限为0600,每个 Key 明文只来自创建响应。
Failure modes
PLAN_NOT_ACTIVE:先发布 Strategy、创建 Binding,再发布 Plan。- 重跑遇到唯一性冲突:不要用新的外部用户 ID 绕开;按 API 契约读取既有资源或采用应用自己的幂等工作流。
- Provision 响应没有
apiKey:已有 Access 不会重放旧 Key 明文;显式创建新 Inference Key。
Security notes
Management Key 只在应用后端使用。处理完 Key 文件后,经批准的 Secret 交付通道传给用户并安全删除临时副本;不要写入日志、Analytics 或数据库明文字段。
Next steps
把 OpenAI JavaScript、Python 或 OpenCode Recipe 交给最终用户。