Skip to content

Provision Free and Pro SaaS users

Create Free and Pro quota plans and provision one application user onto either plan.

Updated View as Markdown
Audience: application-backendPlane: adminAuth: management-keyStatus: beta

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

Terminalsh
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

provision-saas-users.mjsjs
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;
}
Terminalsh
node provision-saas-users.mjs

示例额度用于说明流程,不是产品定价建议;Quota Unit 表达 AI 使用权,不是钱。

Check your work

  • 两个 Plan 为 ACTIVE,各绑定一个已发布 QuotaStrategy。
  • 两条 WorkspaceAccess 的 externalUserIdplanId 和当前 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 JavaScriptPythonOpenCode Recipe 交给最终用户。

Navigation

Type to search…

↑↓ navigate↵ selectEsc close