Goal
无损轮换一名用户的 Inference Key。
Outcome
新 Key 仍绑定同一 WorkspaceAccess;既有 Plan、QuotaPeriod 和历史 Usage 不复制、不归零。验证新 Key 后才撤销旧 Key。
Prerequisites
- Access ID、旧 Key ID、Management Key。
- 既有 Gateway Base URL 和目标 Provider 接受的模型名。
Lifecycle note
Key 只负责凭证安全生命周期。轮换不会延长 WorkspaceAccess.accessEndsAt,也不会改变 QuotaPeriod.periodEnd。
1. Issue, verify, and cut over
import { writeFile } from 'node:fs/promises';
const adminBaseUrl = required('NEXUS_ADMIN_BASE_URL').replace(/\/$/, '');
const managementKey = required('NEXUS_MANAGEMENT_KEY');
const accessId = required('NEXUS_ACCESS_ID');
const oldKeyId = required('NEXUS_OLD_KEY_ID');
const gatewayBaseUrl = required('NEXUS_GATEWAY_BASE_URL').replace(/\/$/, '');
const model = required('NEXUS_MODEL');
async function admin(path, { method = 'GET', body } = {}) {
const response = await fetch(`${adminBaseUrl}${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();
}
const before = await admin(`/admin/v1/workspace-accesses/${accessId}/quota`);
const replacement = await admin(`/admin/v1/workspace-accesses/${accessId}/api-keys`, {
method: 'POST',
body: { name: 'rotation-replacement', expiresInSeconds: 2592000 },
});
await writeFile('replacement-inference-key.txt', `${replacement.key}\n`, { mode: 0o600, flag: 'wx' });
const smoke = await fetch(`${gatewayBaseUrl}/chat/completions`, {
method: 'POST',
headers: { Authorization: `Bearer ${replacement.key}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ model, messages: [{ role: 'user', content: 'Key rotation smoke test.' }] }),
});
if (!smoke.ok) throw new Error(`replacement key smoke test failed: ${smoke.status} ${await smoke.text()}`);
await admin(`/admin/v1/workspace-accesses/${accessId}/api-keys/${oldKeyId}`, { method: 'DELETE' });
const after = await admin(`/admin/v1/workspace-accesses/${accessId}/quota`);
if (before.quotaPeriodId !== after.quotaPeriodId || before.grantedUnits !== after.grantedUnits) {
throw new Error('key rotation unexpectedly changed the quota period');
}
console.log(JSON.stringify({ newKeyId: replacement.id, quotaPeriodId: after.quotaPeriodId }));
function required(name) {
const value = process.env[name]?.trim();
if (!value) throw new Error(`${name} is required`);
return value;
}Check your work
- 新 Key 的烟雾测试成功后旧 Key 才被撤销。
quotaPeriodId和grantedUnits不变;烟雾测试本身可以正常增加consumedUnits。replacement-inference-key.txt权限为0600,明文不会再次从 Admin API 读取。
Failure modes
- 新 Key 测试失败:保留旧 Key,不执行撤销步骤。
workspace_access_denied:修复 Access,而不是继续轮换 Key。- Key 到期时间超出 Access 上限:选择更短 TTL;Key 有效期不能替代 Access 有效期。
Security notes
切流后从部署 Secret 中移除旧 Key,再撤销服务端记录。不要同时撤销所有可用 Key,除非这是明确的紧急处置。
Next steps
复核 Credential model 和 Security。