@ax-hub/admin-sdk
v3.0.0
Published
Governance SDK for AX Hub (platform/tenant admin surface). Shares @ax-hub/core with @ax-hub/sdk.
Downloads
1,026
Readme
@ax-hub/admin-sdk
Governance SDK for AX Hub — the platform/tenant admin surface (tenants CRUD, authz, audit, identity-providers, app-category CUD). Companion to @ax-hub/sdk, which holds the app-developer / member surface.
These resources were split out of @ax-hub/sdk in 1.0.0: member/PAT tokens always received 403 from them, so they don't belong in the agent-facing SDK. They share the same private @ax-hub/core infrastructure (typed errors, branded ids, auth, http, pagination, retry, redaction), inlined at build time — so the error-handling surface matches @ax-hub/sdk exactly.
Upgrading from
@ax-hub/sdk0.x where these lived onsdk.tenants.*/sdk.authz.*/sdk.audit.*/sdk.identity.idp/sdk.apps.categories.*? Seedocs/MIGRATION-1.0.mdfor the full old→new mapping table.
Install
npm install @ax-hub/admin-sdkAdminClient
import { AdminClient } from '@ax-hub/admin-sdk'
const admin = new AdminClient({
token: process.env.AX_HUB_ADMIN_TOKEN!,
tokenType: 'jwt', // 'pat' | 'jwt'
})AdminClient mirrors AxHubClient's construction (token/tokenType validated eagerly at construction) and lazy-getter pattern, but exposes only admin-gated resources. Override baseUrl for staging / self-hosted / local dev. JWT refresh is supported via onRefresh.
const admin = new AdminClient({
baseUrl: 'https://staging-api.example.com',
token: jwt,
tokenType: 'jwt',
onRefresh: async () => fetchFreshJwt(),
})Agent operating notes
Use @ax-hub/admin-sdk only when the token is authorized for governance routes. Ordinary member/PAT workflows for creating apps, dynamic tables, rows, and deployments belong in @ax-hub/sdk. If an agent sees 403 permission_denied or not_admin, treat it as an authorization result, not an SDK bug.
The app/data SDKs were live-verified against production tenant test on 2026-06-08. That QA created and deleted apps, env vars, tables, columns, grants, and rows. For app/data cleanup semantics, table grant revocation is proven by revokedAt, while row/table delete is proven by follow-up 404 or 410. Admin-specific tenant governance calls still require a real admin token and should be run only against disposable tenant fixtures.
Safety checklist for admin agents:
- Confirm the token is intended for admin/governance work.
- Use timestamp-suffixed fixture slugs, domains, tags, and subjects.
- Delete only fixtures created by the current run.
- Branch on typed
AxHubErrorcategory/code, not localizedmessagetext. - Never print admin tokens or raw OAuth/client secrets.
Resource Catalog
| Namespace | Methods |
|-----------|---------|
| admin.tenants | create, list, update, delete, signIconUploadURL |
| admin.tenants.members.forTenant(t) | list, update, deactivate, reactivate |
| admin.tenants.invitations.forTenant(t) | list, create, bulkCreate, delete |
| admin.tenants.emailDomains.forTenant(t) | list, create, delete |
| admin.authz | tenant(t) → subjects (list/create/get), grants (list/create/get/revoke), presets (list/get/create/update/delete) |
| admin.audit | events.forTenant(t).{list,get}, integrityCheck(t), anonymize(t, input) |
| admin.identityProviders | list(tenantId), create(tenantId, input), enable(tenantId, id), disable(tenantId, id) |
| admin.categories | create(t, input), update(t, id, patch), delete(t, id) |
App-category reads (
list/get) stay on@ax-hub/sdkassdk.apps.categories.list/get. Only CUD is admin-gated here.
Tenant-scoped governance
Most authz/audit routes are tenant-path-scoped. Use admin.tenant(tenantIdOrSlug) to bind the tenant once:
const acme = admin.tenant('acme')
await acme.authz.subjects.list()
await acme.authz.grants.list()
await acme.authz.presets.list()
await acme.audit.integrityCheck()Equivalent top-level forms also exist where the tenant is the first argument:
await admin.audit.integrityCheck('acme')
await admin.audit.events.forTenant('acme').list()Tenants CRUD
const t = await admin.tenants.create({ slug: 'acme', name: 'Acme Inc' })
await admin.tenants.update(t.id, { name: 'Acme International' })
const members = await admin.tenants.members.forTenant('acme').list()
await admin.tenants.invitations.forTenant('acme').create({ email: '[email protected]', role: 'member' })
await admin.tenants.emailDomains.forTenant('acme').create({ domain: 'acme.com' })Identity providers
await admin.identityProviders.list(tenantId)
const idp = await admin.identityProviders.create(tenantId, { kind: 'saml', /* ... */ })
await admin.identityProviders.enable(tenantId, idp.id)
await admin.identityProviders.disable(tenantId, idp.id)App categories (CUD)
const cat = await admin.categories.create('acme', { name: 'Finance' })
await admin.categories.update('acme', cat.id, { name: 'Finance & Ops' })
await admin.categories.delete('acme', cat.id)Errors
Every /api/v1/* 4xx/5xx becomes a typed AxHubError subclass — the same surface as @ax-hub/sdk (re-exported from the shared @ax-hub/core). Branch on error.category / error.code, never on the localized message.
import { ForbiddenError, ConflictError } from '@ax-hub/admin-sdk'
try {
await admin.tenants.create({ slug: 'taken', name: 'X' })
} catch (e) {
if (e instanceof ConflictError) {/* slug taken */}
if (e instanceof ForbiddenError) {/* token lacks admin role */}
}Not included
authz.evaluator/decide— removed, no backend route. Will be reintroduced here if the backend exposes an official route.
License
Apache-2.0. See LICENSE.
