Skip to main content
Baobyte AML Suite
Documentation contents

API reference

IAM

22 operations in this area of the contract. Every one declares the permission it requires.

Contract version 0.2.0All areas

Operations

GET/v1/iam/impersonation-grants

Baobyte support sessions opened into this tenant

Required permission: iam.impersonation.readlistImpersonationGrants

Newest first, live and historic. Readable by the **tenant**, which is the half of Q16 that makes the other half checkable: "visible to the tenant" has to mean a screen the institution can open, not a report they can request from the vendor.

The page cannot be resumed; the service takes a limit and no cursor, so a truncated list says so rather than looking complete.

Show parameters and responses

Parameters

NameInTypeDescription
limitqueryintegerPage size, capped at 200.

Responses

  • 200Support sessionsImpersonationGrantPage
POST/v1/iam/impersonation-grants

Open a support session

Required permission: iam.impersonation.writegrantImpersonation

Time-boxed, reason-coded, and recorded in the hash chain on grant, on every use and on revoke (ADR-0003, Q12). Until now the grant and revoke paths were reachable only from inside a test, so Baobyte support could not actually open a session in production, and `iam.impersonation.granted` and `iam.impersonation.revoked` were emitted nowhere.

The session **confers no permission the operator does not already hold**. It is a front door that is watched, not a back door: presenting the grant id in `X-Baobyte-Impersonation-Grant` marks the session and stamps every audit row it writes with the grant, and `GET /v1/me` reports `impersonating: true` with the grant id so the UI can tell the customer that support is in their data.

`platform_actor` is the authenticated caller and is never taken from the body: the one field an examiner reads to find out *who* was in the data has to be unforgeable.

**Honest limit.** A grant is scoped to the tenant the caller is authenticated in — the storage carries `tenant_id` and RLS — so there is no cross-tenant platform console. An operator can only open a session into a tenant they have been provisioned a user in. That floor is intentional: every session is preceded by the customer's own administrator creating the account, which is itself a consent record in the chain.

Show parameters and responses

Responses

  • 201The open sessionImpersonationGrant
  • 422—
POST/v1/iam/impersonation-grants/{grantID}/revoke

End a support session early

Required permission: iam.impersonation.writerevokeImpersonation

The next request presenting this grant is refused with `impersonation-not-live`, not silently downgraded to an ordinary session. Emits `iam.impersonation.revoked`.

Revoking an already-expired grant succeeds: "who ended the support session, and when" is asked after the window has closed, not during it.

Show parameters and responses

Parameters

NameInTypeDescription
grantIDrequiredpathstring—

Responses

  • 204Revoked
  • 404—
GET/v1/iam/org-units

The organisational hierarchy

Required permission: iam.orgunit.readlistOrgUnits

Ordered by code. The tree is bounded by the institution's own structure, so the page is complete rather than truncated.

Show parameters and responses

Responses

  • 200Org unitsOrgUnitPage
POST/v1/iam/org-units

Create an organisational unit

Required permission: iam.orgunit.writecreateOrgUnit

BRD §7.2 names "user roles, permissions, and organizational hierarchy" together. The table has existed since migration 00003 with a self-referencing composite foreign key, RLS and two indexes, and was reachable from no route, no CLI and no screen.

`code` is uppercase by constraint because a unit code is printed on reports and read aloud in meetings, and mixed case turns `HQ-Comp` and `HQ-COMP` into two units. `parent_id` must name a unit in the same tenant; the composite foreign key is what refuses anything else.

The `org_units` tree in the `iam.access_policy` configuration document is **not** reconciled into these rows. Nothing materialises it, and a reconciler that deleted units a document no longer named would orphan every user assigned to them.

Show parameters and responses

Responses

  • 201The created unitOrgUnit
  • 409—
  • 422—
GET/v1/iam/permissions

The global permission catalogue

Required permission: iam.permission.readlistPermissions

Delivered by content pack. A tenant composes roles from it but cannot invent a permission the code does not enforce.

Show parameters and responses

Responses

  • 200Permission cataloguePermissionPage
  • 403—
GET/v1/iam/roles

Roles and their permission sets

Required permission: iam.role.readlistRoles
Show parameters and responses

Responses

  • 200RolesRolePage
  • 403—
POST/v1/iam/roles

Create a tenant role

Required permission: iam.role.writecreateRole
Show parameters and responses

Responses

  • 201Role createdRole
  • 409—
  • 422—
PATCH/v1/iam/roles/{roleID}

Update a tenant role

Required permission: iam.role.writeupdateRole

System roles delivered by a content pack are refused. A tenant needing a variant creates its own role, so a pack upgrade can correct a regulatory permission set without merging tenant edits into it (ADR-0006).

Show parameters and responses

Parameters

NameInTypeDescription
roleIDrequiredpathstring—

Responses

  • 200Role updatedRole
  • 404—
  • 409The role is delivered by a content pack and cannot be modified
GET/v1/iam/service-accounts

List tenant machine identities

Required permission: iam.service_account.managelistServiceAccounts

Returns safe metadata only. Credential digests and bearer secrets are never part of a read response. Results are tenant-scoped and resumable by id.

Show parameters and responses

Parameters

NameInTypeDescription
limitqueryintegerPage size, capped at 200.
cursorquerystringOpaque keyset cursor from a previous response. Offset pagination is deliberately not offered: `OFFSET 500000` is a table scan, and if it were offered it would be used (ADR-0012). Only the collections that document this parameter can be resumed. The rest report `truncated: true` and refuse a cursor rather than silently returning the first page again — see the `truncated` field on the page schemas.
statusqueryactive | suspended | revoked—

Responses

  • 200A page of service accountsServiceAccountPage
  • 403—
POST/v1/iam/service-accounts

Create a service account and issue its first opaque credential

Required permission: iam.service_account.managecreateServiceAccount

The `credential` is disclosed exactly once in this response and cannot be recovered. Store it in the calling system's secret manager before leaving this response. Baobyte stores only a SHA-256 digest of 32 uniformly random bytes.

This is a Baobyte opaque API credential, not OAuth client_credentials and not an external authorization-server token. Assigned roles must contain only the closed machine-safe permission set: `monitoring.transaction.ingest` and `monitoring.batch.ingest`.

Show parameters and responses

Responses

  • 201Created; the bearer appears only in this responseIssuedServiceAccount
  • 409—
  • 422—
GET/v1/iam/service-accounts/{serviceAccountID}

Read safe service-account and credential metadata

Required permission: iam.service_account.managegetServiceAccount
Show parameters and responses

Parameters

NameInTypeDescription
serviceAccountIDrequiredpathstring—

Responses

  • 200Service-account metadata; never a bearer or digestServiceAccount
  • 404—
PATCH/v1/iam/service-accounts/{serviceAccountID}

Rename, suspend, reactivate or change expiry

Required permission: iam.service_account.manageupdateServiceAccount

Revocation is terminal and uses the dedicated revoke operation.

Show parameters and responses

Parameters

NameInTypeDescription
serviceAccountIDrequiredpathstring—

Responses

  • 200Updated metadataServiceAccount
  • 404—
  • 409—
  • 422—
POST/v1/iam/service-accounts/{serviceAccountID}/revoke

Permanently revoke a service account and every current credential

Required permission: iam.service_account.managerevokeServiceAccount

Idempotent. A revoked service account cannot be reactivated or rotated.

Show parameters and responses

Parameters

NameInTypeDescription
serviceAccountIDrequiredpathstring—

Responses

  • 200Revoked service accountServiceAccount
  • 404—
PUT/v1/iam/service-accounts/{serviceAccountID}/roles/{roleID}

Assign an ingest-only role to a service account

Required permission: iam.service_account.manageassignServiceAccountRole

Refuses a role that is empty or grants any permission outside the two-key machine allowlist. Role permissions are checked again on every authentication, so later role widening fails closed.

Show parameters and responses

Parameters

NameInTypeDescription
serviceAccountIDrequiredpathstring—
roleIDrequiredpathstring—

Responses

  • 200Service account with resulting rolesServiceAccount
  • 404—
  • 409—
  • 422—
DELETE/v1/iam/service-accounts/{serviceAccountID}/roles/{roleID}

Remove a role while retaining at least one ingest-only role

Required permission: iam.service_account.manageunassignServiceAccountRole
Show parameters and responses

Parameters

NameInTypeDescription
serviceAccountIDrequiredpathstring—
roleIDrequiredpathstring—

Responses

  • 200Service account with resulting rolesServiceAccount
  • 404—
  • 409—
  • 422—
POST/v1/iam/service-accounts/{serviceAccountID}/rotate

Revoke the old credential and issue a new one

Required permission: iam.service_account.managerotateServiceAccountCredential

Rotation is atomic. The old credential is immediately revoked when the transaction commits, and the new bearer is disclosed exactly once in this non-cacheable response.

Show parameters and responses

Parameters

NameInTypeDescription
serviceAccountIDrequiredpathstring—

Responses

  • 201Rotated; the new bearer appears only in this responseIssuedServiceAccount
  • 404—
  • 409—
  • 422—
GET/v1/iam/users

List users

Required permission: iam.user.readlistUsers
Show parameters and responses

Parameters

NameInTypeDescription
limitqueryintegerPage size, capped at 200.
cursorquerystringOpaque keyset cursor from a previous response. Offset pagination is deliberately not offered: `OFFSET 500000` is a table scan, and if it were offered it would be used (ADR-0012). Only the collections that document this parameter can be resumed. The rest report `truncated: true` and refuse a cursor rather than silently returning the first page again — see the `truncated` field on the page schemas.
statusqueryactive | suspended | deactivated—

Responses

  • 200A page of usersUserPage
  • 403—
POST/v1/iam/users

Provision a user

Required permission: iam.user.writecreateUser

Users are provisioned deliberately rather than created on first login. In an AML platform, who has access is a controlled decision, so a valid token for an unknown subject is refused rather than auto-enrolled.

Show parameters and responses

Responses

  • 201User provisionedUser
  • 409—
  • 422—
PATCH/v1/iam/users/{userID}

Suspend, reactivate, deactivate or move a user

Required permission: iam.user.writeupdateUser

**The leaver path.** The authenticator refuses any principal whose user row is not `active`, so this is the only operation that can end somebody's access without deleting an audit actor — and until now it did not exist: user lifecycle over the API stopped at create, while `status` and its three states had been in the schema since Phase 1.

A suspended or deactivated user's next request is refused with the same `not-provisioned` problem an unknown subject gets. That is deliberate: telling a rejected caller "that account is suspended" confirms the account exists.

`org_unit_id` moves the user; `clear_org_unit: true` detaches them. Both are carried here rather than on a separate endpoint because "moved to Compliance" and "left the company" are changes to the same row and belong in the same audit event. An empty body is refused rather than recording an event that nothing changed.

Deactivation emits `iam.user.deactivated`; anything else emits `iam.user.updated`, so an examiner filtering for "who lost access last quarter" is not reading display-name edits.

Show parameters and responses

Parameters

NameInTypeDescription
userIDrequiredpathstring—

Responses

  • 200The updated userUser
  • 404—
  • 422—
POST/v1/iam/users/{userID}/roles/{roleID}

Grant a role to an existing user

Required permission: iam.user.writeassignUserRole

Under `iam.user.write`, not `iam.role.write`. Composing what a role *means* and deciding *who holds one* are different duties: an administrator who may hand out roles should not thereby be able to redefine what those roles can do.

Idempotent — granting a role the user already holds succeeds and changes nothing. The response is the whole user with the resulting role set, because what matters to a reviewer is what the person can do now, not which single grant moved.

Show parameters and responses

Parameters

NameInTypeDescription
userIDrequiredpathstring—
roleIDrequiredpathstring—

Responses

  • 200The user with the role grantedUser
  • 404—
DELETE/v1/iam/users/{userID}/roles/{roleID}

Revoke a role from a user

Required permission: iam.user.writeunassignUserRole

Emits `iam.role.unassigned`. Both that action and its counterpart have been in the audit vocabulary since Phase 1 and were emitted nowhere, because there was no route that could change a grant after the user existed.

Revoking a role the user does not hold is a `404`, not a silent success: an administrator removing access needs to know whether the access was there.

Show parameters and responses

Parameters

NameInTypeDescription
userIDrequiredpathstring—
roleIDrequiredpathstring—

Responses

  • 200The user with the role revokedUser
  • 404—