@ai-setu/admin
Read this first, agent. This package = control plane, not inference. Use it to manage a tenant's own workspaces, API keys, members, billing, PATs, and BYOK provider credentials. Shape mirror the OpenAI SDK ergonomic — one import, one symbol, env-var-first constructor — so if you know
@ai-setu/client, you pick this up by analogy. Want chat / embeddings? Wrong package — use@ai-setu/client.
TL;DR
npm i @ai-setu/admin
export AI_SETU_PAT=tt_pat_…import { AiSetuAdmin } from '@ai-setu/admin';
const admin = new AiSetuAdmin(); // read AI_SETU_PAT from env
const ws = await admin.workspaces.list();
const key = await admin.apiKeys.create({ workspaceId: ws[0].id, name: 'CI' });
console.log('store this once, it no come back:', key.secret);Rules for agent
- Auth = PAT, not workspace key. Env
AI_SETU_PAT(shapett_pat_…), ornew AiSetuAdmin({ pat }). Thett_live_…key is for inference only — it no work here. - PAT carry a scope.
admin:read= list/read only.admin:write= mutations (keys, members, billing). Wrong scope →UNAUTHORIZED. - Secrets show once.
apiKeys.create/pats.createreturn the secret one time. Capture it now or lose it forever. - Every method take optional
AbortSignalas last arg. - No inference here. Balance + usage live here; chat + embeddings do not.
Namespaces — the whole surface
| Namespace | Methods |
|---|---|
admin.workspaces |
list, get, create, rename, delete, listMembers |
admin.apiKeys |
list(workspaceId), create, rotate, revoke |
admin.members |
invite, setRole, remove |
admin.billing |
creditBalance(tenantId), createTopUpIntent, topUps |
admin.usage |
projection(tenantId) |
admin.pats |
list, create, revoke |
admin.providerCredentials |
availability, list, upsert, test, revoke |
BYOK Connections
providerCredentials.upsert creates a Connection (a slug handle) backed
by a Credential (the secret), so inference bills against your upstream
account, not the platform's. Pass slug — the per-request routing handle used
in @<slug>/<model> model strings (label still works as a deprecated alias;
slug wins). OpenAI / Anthropic carry an apiKey; AWS Bedrock carries the
awsAccessKeyId / awsSecretAccessKey / awsRegion triple instead (gateway
SigV4-signs your Bedrock traffic). Secrets encrypt at rest under the platform
KEK, never come back — only a keyHint (last four) returns. Returned objects
carry slug.
// AWS Bedrock BYOK — reached per request as model "@bedrock-prod/<model>"
const cred = await admin.providerCredentials.upsert({
provider: 'bedrock',
slug: 'bedrock-prod',
awsAccessKeyId: process.env.AWS_ACCESS_KEY_ID,
awsSecretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
awsRegion: 'us-west-2',
});
// Verify the keys authenticate (STS GetCallerIdentity — no quota burn)
const probe = await admin.providerCredentials.test(cred.id);
if (!probe.ok) throw new Error(probe.message);
// Now inference route through your AWS account:
// client.chat.completions.create({ model: 'bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0', … })
await admin.providerCredentials.revoke(cred.id);OpenAI / Anthropic stay key-based:
await admin.providerCredentials.upsert({
provider: 'openai',
slug: 'openai-prod', // reached per request as model "@openai-prod/<model>"
apiKey: process.env.OPENAI_API_KEY,
});Errors
import { AdminGraphQLError, AdminHttpError } from '@ai-setu/admin';
try {
await admin.workspaces.create({ name: 'staging' });
} catch (err) {
if (err instanceof AdminGraphQLError && err.code === 'UNAUTHORIZED') {
// PAT lack the permission.
} else if (err instanceof AdminHttpError && err.status === 401) {
// PAT revoked / expired.
}
}Get a PAT
Mint one from the dashboard: https://app.aisetu.ai/settings/profile
(Personal Access Tokens). Or have a builder agent run the OTP flow through
@ai-setu/mcp. Scopes:
admin:read— list workspaces, balance, usageadmin:write— mutations (keys, members, billing top-ups)
Environments
Default = production (api.aisetu.ai). Point at a different control plane (e.g. a
self-hosted deployment) with a full URL override:
export AI_SETU_API_BASE_URL=https://api.your-deployment.example.comPrecedence: new AiSetuAdmin({ baseUrl }) → AI_SETU_API_BASE_URL (full URL)
→ production.
Runtime support
- Node ≥ 20 — shared keep-alive
undici.Agent, sequential calls reuse the TCP connection. - Edge (Cloudflare Workers / Vercel) — platform fetch (already pool).
undiciis optional dep, edge bundlers skip it.
Under the hood (you no need this to use it)
Transport = hand-coded GraphQL strings over raw fetch. Ship <2 KB vs ~25 KB
for the smallest GraphQL client, edge-safe with no polyfill. Ops live in
src/operations/*.ts — easy to read or fork.
Every shipped op validate against a committed snapshot of the API's public
schema (schema/schema-public.gql) in src/__tests__/schema-drift.test.ts, so
a query can't silently drift from the API. graphql-codegen derive the public
result types from that same snapshot — result shape can never diverge from what
its query select. Refresh snapshot after an intentional API change (running
ai-setu-api container needed):
pnpm --filter @ai-setu/admin schema:export
pnpm --filter @ai-setu/admin codegen # also run automatically on buildThe four AI Setu packages — which one you grab
| Package | Use it when |
|---|---|
@ai-setu/client |
Runtime agent / app call inference. Drop-in for openai. |
@ai-setu/admin |
Scripts / CI / builder agent run control-plane ops in TypeScript. |
@ai-setu/cli |
Same control-plane ops from the shell. |
@ai-setu/mcp |
Builder agent (Claude Code, Cursor) drive onboarding + ops conversationally. |
Full agent runbook = llms.txt.
License
MIT.