BetterIAM
Server API

config

Configuration as code: export a tenant's access model as one JSON document, review changes to it, and apply it the same way to staging and production.

Configuration as code: export a tenant's access model as one JSON document, review changes to it, and apply it the same way to staging and production. Hand-edited roles drift between environments and nobody can say who changed what; a document in version control gives you pull-request review, a dry run before every change, and a pipeline check that fails when someone edits production by hand. See configuration as code.

The configuration document

A document has version: 1 and any of these lists, each keyed by name (never by ID), so the same file works in every environment:

KeyWhat it holds
policiesPolicies with their documents.
rolesRoles with attached policy names, an inline permissions list or document, and inherited role names.
groupsGroups, optionally with members as email addresses (membership is then made to match exactly).
bindingsGroup role bindings, with eligibility, activation rules, approver group, and access window.
resourceTypesTenant-defined resource types (only with permissions.mode: 'tenant-defined').
packagesAccess packages with their roles, groups, request settings, and optional automatic-assignment rule.
accessPolicyThe tenant's activation floors for eligible bindings ({} clears them).
invariantsAccess invariants, naming groups by name and people by email.
agreementsTerms-of-use agreements; a content change publishes a new version everyone accepts again.
departmentsDepartments by name, with code, parent (a name), head and members (emails), and costCenter.
teamsTeams by slug, with parent (a slug), department (a name), join settings, maintainers and members (emails), and the roles they hold as standing bindings.

Runtime state stays out of the document: identities, their direct role bindings, credentials, webhooks, who holds which package, temporary team memberships, and join requests. Team backing groups (team:{slug}) never appear under groups or bindings: a team's access is its roles list. The protected Owner role and policy are never exported or changed.

How planning and pruning work

Only the kinds a document lists are compared. A kind the document leaves out is left alone, so a file with just roles never touches groups. Within a listed kind, items are matched by name and reported as create, update (with the changed fields, before, and after), or unchanged. Items of a listed kind that the document omits are deleted only when you pass prune: true. References between items (a role's policies, a binding's group and role, a package's roles) must exist in the document or in the tenant after the apply, or the plan fails with INVALID_INPUT naming the problem.

Methods3
Serveriam.api.config
Clientclient.config
HTTPPOST /api/iam/config/*
MethodWhat it doesAccess
applyApplies a configuration document to the tenant in one transaction, and returns the changes it made.Credential
exportReturns the tenant's roles, policies, groups, bindings, and the other configuration kinds as a document that plan and apply accept.Credential
planShows, without writing anything, every change apply would make for a configuration document.Credential

apply

Applies a configuration document to the tenant in one transaction, and returns the changes it made.

POST/api/iam/config/apply
client.config.apply()Credential
  • Permission: iam:config:apply on the tenant, plus the permission of the equivalent direct call for every change (for example iam:roles:create on the tenant, iam:policies:update on the policy, iam:bindings:create on the role, iam:groups:update on the group, iam:tenants:update for the access policy). New roles, policies, and bindings are created under your grant authority.
  • Audited as: iam:config:apply, and config:apply with metadata prune, the change counts, and changed (one line per change).
  • Errors: INVALID_INPUT for a malformed document or an unknown reference; INVALID_POLICY or INVALID_ACTION for a policy document storage would reject; ACCESS_DENIED naming the first change you are not allowed to make; GRANT_AUTHORITY_REQUIRED when something must be created and you hold no grant authority; LIMIT_EXCEEDED when a create exceeds the tenant's plan limits; SOD_CONFLICT when the result gives someone roles a separation-of-duties rule forbids together; INVARIANT_VIOLATION when it would newly break an enforced access invariant.

Every change is authorized exactly like the direct API call, so the document can never do more than you could do by hand, and one failure rolls everything back: the tenant is never left half-applied. Changes run in dependency order: policies, roles, and groups are created before the bindings and packages that name them, and are deleted only after those are gone. Changing a group binding's eligibility or activation rules replaces the binding, which ends its current activations.

Always plan first and review the result. The config-apply CLI command runs this call from a pipeline.

const document = JSON.parse(await readFile('tenant.json', 'utf8'));
const plan = await iam.api.config.plan(credential, { tenantId, config: document });
if (plan.summary.delete === 0) {
  const result = await iam.api.config.apply(credential, { tenantId, config: document });
  console.log(result.summary); // { create: 2, update: 1, delete: 0, unchanged: 14 }
}
Input

Prop

Type

Returns

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/config/apply" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "config": {}
}'
Signature
iam.api.config.apply(
  credential: CredentialInput,
  input: { tenantId: string; config: unknown; prune?: boolean },
): Promise<{
  applied: true;
  tenantId: string;
  prune: boolean;
  changes: ConfigChange[];
  summary: Record<ConfigChangeAction, number>;
}>

export

Returns the tenant's roles, policies, groups, bindings, and the other configuration kinds as a document that plan and apply accept.

POST/api/iam/config/export
client.config.export()Credential
  • Permission: iam:config:read on the tenant.
  • Audited as: iam:config:read.

Use it to bootstrap version control from a tenant configured by hand, or to copy one environment's model into another. Group members are exported as lowercase email addresses (members without an email are left out), a role whose inline document was written as a permissions list is exported as that list again, and invariants, agreements, departments, and teams appear only when the tenant has some. The config-export CLI command writes it to a file.

Input

Prop

Type

Returns

A TenantConfig object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/config/export" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.config.export(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<TenantConfig>

plan

Shows, without writing anything, every change apply would make for a configuration document.

POST/api/iam/config/plan
client.config.plan()Credential
  • Permission: iam:config:read on the tenant.
  • Audited as: iam:config:read.
  • Errors: INVALID_INPUT for a malformed document (wrong version, duplicate names, more than 1000 items in a list) or an unknown reference.

The result lists each change with its kind, name, and action, and a summary with counts per action. Planning does not check whether you may make each change; apply does. Run it in CI on every pull request, and use the config-plan CLI command with --fail-on-drift in a nightly job to catch changes someone made by hand.

Input

Prop

Type

Returns

A ConfigPlan object:

Prop

Type

Example HTTP request

Only the required fields are shown; replace each <placeholder>. The response is { "data": … } on success or { "error": { "code", "message" } }.

curl -X POST "$IAM_URL/api/iam/config/plan" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "config": {}
}'
Signature
iam.api.config.plan(
  credential: CredentialInput,
  input: { tenantId: string; config: unknown; prune?: boolean },
): Promise<ConfigPlan>

Was this page helpful?

Better IAM is created by Sean Filimon

Last updated

On this page