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
/v1/iam/impersonation-grantsBaobyte support sessions opened into this tenant
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 responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit | query | integer | Page size, capped at 200. |
Responses
200Support sessionsImpersonationGrantPage
/v1/iam/impersonation-grantsOpen a support session
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 responsesHide parameters and responses
Responses
201The open sessionImpersonationGrant422—
/v1/iam/impersonation-grants/{grantID}/revokeEnd a support session early
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 responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
grantIDrequired | path | string | — |
Responses
204Revoked404—
/v1/iam/org-unitsThe organisational hierarchy
Ordered by code. The tree is bounded by the institution's own structure, so the page is complete rather than truncated.
Show parameters and responsesHide parameters and responses
Responses
200Org unitsOrgUnitPage
/v1/iam/org-unitsCreate an organisational unit
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 responsesHide parameters and responses
Responses
201The created unitOrgUnit409—422—
/v1/iam/permissionsThe global permission catalogue
Delivered by content pack. A tenant composes roles from it but cannot invent a permission the code does not enforce.
Show parameters and responsesHide parameters and responses
Responses
200Permission cataloguePermissionPage403—
/v1/iam/rolesRoles and their permission sets
Show parameters and responsesHide parameters and responses
Responses
200RolesRolePage403—
/v1/iam/rolesCreate a tenant role
Show parameters and responsesHide parameters and responses
Responses
201Role createdRole409—422—
/v1/iam/roles/{roleID}Update a tenant role
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 responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
roleIDrequired | path | string | — |
Responses
200Role updatedRole404—409The role is delivered by a content pack and cannot be modified
/v1/iam/service-accountsList tenant machine identities
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 responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit | query | integer | Page size, capped at 200. |
cursor | query | string | Opaque 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. |
status | query | active | suspended | revoked | — |
Responses
200A page of service accountsServiceAccountPage403—
/v1/iam/service-accountsCreate a service account and issue its first opaque credential
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 responsesHide parameters and responses
Responses
201Created; the bearer appears only in this responseIssuedServiceAccount409—422—
/v1/iam/service-accounts/{serviceAccountID}Read safe service-account and credential metadata
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
serviceAccountIDrequired | path | string | — |
Responses
200Service-account metadata; never a bearer or digestServiceAccount404—
/v1/iam/service-accounts/{serviceAccountID}Rename, suspend, reactivate or change expiry
Revocation is terminal and uses the dedicated revoke operation.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
serviceAccountIDrequired | path | string | — |
Responses
200Updated metadataServiceAccount404—409—422—
/v1/iam/service-accounts/{serviceAccountID}/revokePermanently revoke a service account and every current credential
Idempotent. A revoked service account cannot be reactivated or rotated.
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
serviceAccountIDrequired | path | string | — |
Responses
200Revoked service accountServiceAccount404—
/v1/iam/service-accounts/{serviceAccountID}/roles/{roleID}Assign an ingest-only role to a service account
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 responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
serviceAccountIDrequired | path | string | — |
roleIDrequired | path | string | — |
Responses
200Service account with resulting rolesServiceAccount404—409—422—
/v1/iam/service-accounts/{serviceAccountID}/roles/{roleID}Remove a role while retaining at least one ingest-only role
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
serviceAccountIDrequired | path | string | — |
roleIDrequired | path | string | — |
Responses
200Service account with resulting rolesServiceAccount404—409—422—
/v1/iam/service-accounts/{serviceAccountID}/rotateRevoke the old credential and issue a new one
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 responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
serviceAccountIDrequired | path | string | — |
Responses
201Rotated; the new bearer appears only in this responseIssuedServiceAccount404—409—422—
/v1/iam/usersList users
Show parameters and responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit | query | integer | Page size, capped at 200. |
cursor | query | string | Opaque 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. |
status | query | active | suspended | deactivated | — |
Responses
200A page of usersUserPage403—
/v1/iam/usersProvision a user
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 responsesHide parameters and responses
Responses
201User provisionedUser409—422—
/v1/iam/users/{userID}Suspend, reactivate, deactivate or move a user
**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 responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
userIDrequired | path | string | — |
Responses
200The updated userUser404—422—
/v1/iam/users/{userID}/roles/{roleID}Grant a role to an existing user
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 responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
userIDrequired | path | string | — |
roleIDrequired | path | string | — |
Responses
200The user with the role grantedUser404—
/v1/iam/users/{userID}/roles/{roleID}Revoke a role from a user
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 responsesHide parameters and responses
Parameters
| Name | In | Type | Description |
|---|---|---|---|
userIDrequired | path | string | — |
roleIDrequired | path | string | — |
Responses
200The user with the role revokedUser404—