BetterIAM
Server API

policies

Policies are named, versioned policy documents that roles attach to grant access.

Policies are named, versioned policy documents that roles attach to grant access. A policy grants nothing on its own: it takes effect when a role lists it in policyIds and that role is bound to someone. Storing a document as a policy, instead of inline in a role, lets several roles share it, keeps every change as a version you can list and restore, and lets editors test a draft before saving it. This group also holds the administrator-only review reads (simulate, whoCan, effectiveActions) that explain access without creating a session or granting anything. The document format itself is described in policy documents.

Versions and edit rights

  • Every change is a version. A policy starts at version 1. Each update or restoreVersion archives the current version and saves the next one, so listVersions always shows the full history.
  • Optimistic concurrency. update takes the version you read and fails with VERSION_CONFLICT when someone saved in between, so two editors cannot silently overwrite each other.
  • Edits stay with their authority. A policy records the grant authority it was created under. Only that authority's holder, or root, may update, restore, or delete it, and its ceiling keeps bounding the policy wherever it is attached, even when a higher authority attaches it to a role.
  • Validated against the catalog. Documents are checked when they are created, updated, or restored: INVALID_POLICY for a malformed document, INVALID_ACTION for an unknown action, and INVALID_RESOURCE_TYPE for an unknown resource type.
  • The Owner policy is protected. The system Owner policy behind every tenant's Owner role cannot be edited, restored, or deleted (PROTECTED_RESOURCE).

Reading a review result

simulate, whoCan, and effectiveActions evaluate an identity in a synthetic session that is never issued: a user session (an API-key session for service accounts), without MFA unless you pass assumeMfa: true. The ordinary evaluator runs, so conditions, boundaries, authority ceilings, access windows, relationships, and just-in-time eligibility (only live activations count) all apply. Results are advisory: they explain access, they never enforce it. See access reviews.

ReasonMeaning
allowedA role grants the action and no deny or boundary blocks it.
explicit-denyA deny statement in one of the identity's roles matched.
boundary-denyA tenant boundary, principal boundary, or session policy does not allow the action.
NO_APPLICABLE_GRANTNo role grants the action within its authority ceilings.
no-grantReturned by test only: the tested document does not allow the action.
ROOT_OVERRIDEThe identity is a platform root administrator evaluated with MFA; the root override applies.
TENANT_INACTIVEThe tenant or one of its ancestors is not active.
UNKNOWN_ACTIONReturned by simulate only: the action is not in the catalog.
Methods11
Serveriam.api.policies
Clientclient.policies
HTTPPOST /api/iam/policies/*
MethodWhat it doesAccess
createStores a new policy document at version 1 under your grant authority.Credential
deleteDeletes a policy that no role attaches.Credential
effectiveActionsLists which actions one identity could perform on one resource, with a reason per action.Credential
getReturns one policy with its current document and version.Credential
listLists every policy in the tenant at its current version, including the protected Owner policy.Credential
listVersionsReturns a policy's full history, oldest first, ending with the current version.Credential
restoreVersionRolls a policy back to an earlier document by saving that document as a new version.Credential
simulateExplains the decision one identity would get for one action on one resource, without creating a session or granting anything.Credential
testEvaluates an unsaved policy document against one action, resource, and context, for policy editors.Credential
updateSaves a new version of a policy's document, name, or description, keeping the previous version in its history.Credential
whoCanLists every active identity that could perform an action on a resource, with the reason, for access reviews.Credential

create

Stores a new policy document at version 1 under your grant authority.

POST/api/iam/policies/create
client.policies.create()Credential

Used inPolicy documents,Roles and bindings

  • Permission: iam:policies:create on the tenant, plus an active grant authority.
  • Audited as: iam:policies:create.
  • Errors: INVALID_POLICY, INVALID_ACTION, or INVALID_RESOURCE_TYPE when the document does not validate against the catalog; GRANT_AUTHORITY_REQUIRED when you hold no active grant authority; LIMIT_EXCEEDED when the tenant's plan limit for policies is reached; INVALID_INPUT for an empty name or a description over 512 characters.

The new policy grants nothing until a role attaches it with roles.create or roles.update. Try a draft with test first.

const readOwn = await iam.api.policies.create(credential, {
  tenantId,
  name: 'Read own documents',
  document: {
    version: 1,
    statements: [
      {
        sid: 'OwnedDocuments',
        effect: 'allow',
        actions: ['documents:read'],
        resources: ['document/*'],
        conditions: { StringEquals: { 'resource.ownerId': '${principal.id}' } },
      },
    ],
  },
});
Input

A PolicyInput object:

Prop

Type

Returns

A Policy 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/policies/create" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "name": "<name>",
  "document": {
    "version": 1,
    "statements": [
      {
        "effect": "allow",
        "actions": [
          {}
        ],
        "resources": [
          {}
        ]
      }
    ]
  }
}'
Signature
iam.api.policies.create(
  credential: CredentialInput,
  input: PolicyInput,
): Promise<Policy>

delete

Deletes a policy that no role attaches.

POST/api/iam/policies/delete
client.policies.delete()Credential
  • Permission: iam:policies:delete on the policy, and the policy's grant authority (or root).
  • Audited as: iam:policies:delete.
  • Errors: RESOURCE_IN_USE (409) while any role still attaches the policy; PROTECTED_RESOURCE for the Owner policy; ACCESS_DENIED when another administrator's authority created it; NOT_FOUND.

Detach the policy first by updating each role's policyIds. The refusal exists so that deleting a policy never silently removes access from the roles built on it.

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/policies/delete" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "policyId": "<policyId>"
}'
Signature
iam.api.policies.delete(
  credential: CredentialInput,
  input: { tenantId: string; policyId: string },
): Promise<{ deleted: boolean }>

effectiveActions

Lists which actions one identity could perform on one resource, with a reason per action.

POST/api/iam/policies/effectiveActions
client.policies.effectiveActions()Credential

Used inAccess reviews,Sharing and access questions

  • Permission: iam:policies:simulate on the identity (iam/{identityId}).
  • Audited as: iam:policies:simulate.
  • Errors: INVALID_INPUT for more than 200 actions; INVALID_ACTION when one of them is not in the catalog; NOT_FOUND when the identity is not in this tenant or a managed resource is not registered; RESOURCE_RESOLVER_REQUIRED.

Without actions, every action in the catalog is checked: the built-in iam:* actions, your product's and plugins' actions, and tenant-defined ones. The identity's grants are loaded once, so this is cheaper than calling simulate per action. The result has allowed (the sorted action names) and results (each action with allowed and reason), which is what a "what can this person do here?" panel needs.

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/policies/effectiveActions" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "identityId": "<identityId>",
  "resource": {
    "type": "<type>",
    "id": "<id>"
  }
}'
Signature
iam.api.policies.effectiveActions(
  credential: CredentialInput,
  input: {
    tenantId: string;
    identityId: string;
    resource: { type: string; id: string };
    actions?: string[];
    assumeMfa?: boolean;
  },
): Promise<{
  allowed: string[];
  results: { action: string; allowed: boolean; reason: string }[];
}>

get

Returns one policy with its current document and version.

POST/api/iam/policies/get
client.policies.get()Credential
  • Permission: iam:policies:read on the policy.
  • Audited as: iam:policies:read.
  • Errors: NOT_FOUND when the policy is not in this tenant.
Input

Prop

Type

Returns

A Policy 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/policies/get" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "policyId": "<policyId>"
}'
Signature
iam.api.policies.get(
  credential: CredentialInput,
  input: { tenantId: string; policyId: string },
): Promise<Policy>

list

Lists every policy in the tenant at its current version, including the protected Owner policy.

POST/api/iam/policies/list
client.policies.list()Credential
  • Permission: iam:policies:read on the tenant.
  • Audited as: iam:policies:read.
Input

Prop

Type

Returns

An array of Policy.

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/policies/list" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>"
}'
Signature
iam.api.policies.list(
  credential: CredentialInput,
  input: { tenantId: string },
): Promise<Policy[]>

listVersions

Returns a policy's full history, oldest first, ending with the current version.

POST/api/iam/policies/listVersions
client.policies.listVersions()Credential

Used inPolicy documents

  • Permission: iam:policies:read on the policy.
  • Audited as: iam:policies:read.
  • Errors: NOT_FOUND when the policy is not in this tenant.

Each entry carries the document, name, and version as they were. Archived entries have their own record id and point back to the policy through policyId, so pick a version for restoreVersion by its version number.

Input

Prop

Type

Returns

An array of Policy.

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/policies/listVersions" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "policyId": "<policyId>"
}'
Signature
iam.api.policies.listVersions(
  credential: CredentialInput,
  input: { tenantId: string; policyId: string },
): Promise<Policy[]>

restoreVersion

Rolls a policy back to an earlier document by saving that document as a new version.

POST/api/iam/policies/restoreVersion
client.policies.restoreVersion()Credential

Used inPolicy documents,Sharing and access questions

  • Permission: iam:policies:update on the policy, and the policy's grant authority (or root).
  • Audited as: iam:policies:update.
  • Errors: INVALID_INPUT when version is not between 1 and the current version, or is the current version; NOT_FOUND when that version is not in the history; INVALID_ACTION or INVALID_RESOURCE_TYPE when the old document no longer fits the catalog; PROTECTED_RESOURCE; ACCESS_DENIED; INVARIANT_VIOLATION.

History is never rewritten: restoring version 3 of a policy at version 7 saves version 8 with version 3's document. Only the document is restored; the name and description stay as they are. The old document is validated again because actions or resource types may have been removed since it was written.

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/policies/restoreVersion" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "policyId": "<policyId>",
  "version": 1
}'
Signature
iam.api.policies.restoreVersion(
  credential: CredentialInput,
  input: { tenantId: string; policyId: string; version: number },
): Promise<{
  document: PolicyDocument;
  version: number;
  name: string;
  description?: string;
  id: string;
  tenantId: string;
  uniqueKey?: string;
}>

simulate

Explains the decision one identity would get for one action on one resource, without creating a session or granting anything.

POST/api/iam/policies/simulate
client.policies.simulate()Credential

Used inAccess reviews

  • Permission: iam:policies:simulate on the identity (iam/{identityId}).
  • Audited as: iam:policies:simulate.
  • Errors: NOT_FOUND when the identity is not in this tenant or a managed resource is not registered; RESOURCE_RESOLVER_REQUIRED for an application-owned resource type when no resolveResource is configured; RESOURCE_MISMATCH when the resolver returns a record of another tenant or resource.

Use it when support asks "why can't this person open this?". Unlike the public authorization calls, the result includes matched, the statements that decided. An action missing from the catalog returns allowed: false with reason UNKNOWN_ACTION rather than an error. For iam:* actions, pass platform resources such as { type: 'iam', id: roleId }.

const decision = await iam.api.policies.simulate(credential, {
  tenantId,
  identityId: alice.id,
  action: 'invoices:approve',
  resource: { type: 'invoice', id: 'inv_2041' },
  assumeMfa: true,
});
// { allowed: false, reason: 'explicit-deny', matched: [...] }
Input

Prop

Type

Returns

A Decision 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/policies/simulate" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "identityId": "<identityId>",
  "action": "<action>",
  "resource": {
    "type": "<type>",
    "id": "<id>"
  }
}'
Signature
iam.api.policies.simulate(
  credential: CredentialInput,
  input: {
    tenantId: string;
    identityId: string;
    action: string;
    resource: { type: string; id: string };
    assumeMfa?: boolean;
  },
): Promise<Decision>

test

Evaluates an unsaved policy document against one action, resource, and context, for policy editors.

POST/api/iam/policies/test
client.policies.test()Credential

Used inPolicy documents,Sharing and access questions

  • Permission: iam:policies:simulate on the tenant.
  • Audited as: iam:policies:simulate.
  • Errors: INVALID_POLICY, INVALID_ACTION, or INVALID_RESOURCE_TYPE when the document does not validate; INVALID_INPUT when context has more than 200 keys or resource is over 2048 characters.

Only the document is evaluated: no identity, roles, bindings, or boundaries are involved, so it answers "does this document say what I mean?". resource is a type/id string. The context starts with the keys a session issued now would carry (resource.tenantId and principal.tenantId set to the tenant, principal.sessionId, principal.tokenIssueTime, principal.authTime, principal.sessionTagKeys, and request.time); your context adds keys or overrides them. Add 'principal.mfa': true to test an MFA condition and principal.id to resolve ${principal.id} variables. The result is a full decision with matched, the statements that matched as grant:{index}:{sid} (the statement's position when it has no sid).

const decision = await iam.api.policies.test(credential, {
  tenantId,
  document: draft,
  action: 'documents:read',
  resource: 'document/plan-2027',
  context: { 'principal.id': 'usr_123', 'resource.ownerId': 'usr_123' },
});
// { allowed: true, reason: 'allowed', matched: ['grant:0:OwnedDocuments'] }
Input

Prop

Type

Returns

A Decision 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/policies/test" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "document": {
    "version": 1,
    "statements": [
      {
        "effect": "allow",
        "actions": [
          {}
        ],
        "resources": [
          {}
        ]
      }
    ]
  },
  "action": "<action>",
  "resource": "<resource>"
}'
Signature
iam.api.policies.test(
  credential: CredentialInput,
  input: {
    tenantId: string;
    document: PolicyDocument;
    action: string;
    resource: string;
    context?: Record<string, unknown>;
  },
): Promise<Decision>

update

Saves a new version of a policy's document, name, or description, keeping the previous version in its history.

POST/api/iam/policies/update
client.policies.update()Credential

Used inPolicy documents

  • Permission: iam:policies:update on the policy, and the grant authority the policy was created under (or root).
  • Audited as: iam:policies:update.
  • Errors: VERSION_CONFLICT (409) when version is not the current version; ACCESS_DENIED when another administrator's authority created the policy; PROTECTED_RESOURCE for the Owner policy; INVALID_INPUT when none of document, name, or description is given; INVALID_POLICY, INVALID_ACTION, or INVALID_RESOURCE_TYPE for the new document; GRANT_AUTHORITY_REQUIRED; NOT_FOUND; INVARIANT_VIOLATION when the change would newly break an enforced access invariant.

Pass the version you read. Renaming also creates a new version. The change applies at the next request to every role that attaches the policy, so preview it first with impact.preview.

const current = await iam.api.policies.get(credential, { tenantId, policyId });
await iam.api.policies.update(credential, {
  tenantId,
  policyId,
  version: current.version,
  document: {
    version: 1,
    statements: [
      ...current.document.statements,
      { effect: 'deny', actions: ['documents:delete'], resources: ['document/*'] },
    ],
  },
});
Input

A PolicyUpdate object:

Prop

Type

Returns

A Policy 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/policies/update" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "policyId": "<policyId>",
  "version": 1
}'
Signature
iam.api.policies.update(
  credential: CredentialInput,
  input: PolicyUpdate,
): Promise<Policy>

whoCan

Lists every active identity that could perform an action on a resource, with the reason, for access reviews.

POST/api/iam/policies/whoCan
client.policies.whoCan()Credential

Used inAccess reviews,Sharing and access questions

  • Permission: iam:policies:simulate on the tenant.
  • Audited as: iam:policies:simulate.
  • Errors: INVALID_ACTION for an action missing from the catalog; INVALID_INPUT for a kind other than user or service, a limit outside 1 to 1000, or an invalid offset; NOT_FOUND when a managed resource is not registered; RESOURCE_RESOLVER_REQUIRED.

Every active identity of the tenant is evaluated, people and service accounts alike; kind narrows to one. The result has identities (id, name, email, kind, and the decision reason) and total, the number of matches before paging with limit (100 by default) and offset. Grants are loaded per identity, so the cost grows with the directory: use it on review screens, not on every request.

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/policies/whoCan" \
  -H "Authorization: Bearer $BETTER_IAM_TOKEN" \
  -H "Content-Type: application/json" \
  -H "X-Better-IAM: 1" \
  -d '{
  "tenantId": "<tenantId>",
  "action": "<action>",
  "resource": {
    "type": "<type>",
    "id": "<id>"
  }
}'
Signature
iam.api.policies.whoCan(
  credential: CredentialInput,
  input: {
    tenantId: string;
    action: string;
    resource: { type: string; id: string };
    kind?: 'user' | 'service';
    assumeMfa?: boolean;
    limit?: number;
    offset?: number;
  },
): Promise<{ identities: ReviewMatch[]; total: number }>

Was this page helpful?

Better IAM is created by Sean Filimon

Last updated

On this page