Core Administration API
All core-administration routes require a current Bearer session without a pending password change. Request bodies and query strings reject unknown fields. Lists use opaque cursor pagination. Route-specific limits are part of the OpenAPI contract.
Tenants and organization units
Section titled “Tenants and organization units”Tenant routes are available under /v1/tenants. Platform administrators can
create, disable, reactivate, and change every tenant field. A tenant
administrator can change only the name and complete metadata map of their own
active tenant. Other tenant roles can read only tenants granted to them.
GET /v1/tenants is a counted cursor collection. Its default page size is 25,
and it accepts only 25, 50, or 100. The response’s totalCount is the exact
count for the same search, status filter, and authorization scope as items.
Both values come from one database statement and snapshot. Results use stable
ascending createdAt, tenantId order. The opaque cursor is bound to the current
actor, grants, filters, and page size; malformed, stale, oversized, or
incompatible cursors return 400 VALIDATION_FAILED.
Every Tenant response contains the internal UUID tenantId and the public
integer tenantNumber. Tenant selection uses the canonical number, for example
GET /v1/tenants/17 and
GET /v1/tenants/17/organization-units. Existing organization units and
memberships are addressed by their globally unique aggregate IDs at
/v1/organization-units/{organizationUnitId} and
/v1/memberships/{membershipId}. Removed UUID and tenant-nested member paths
have no aliases.
Organization units form a flexible tree below a tenant. Platform and tenant
administrators see the full tree. Organization administrators and viewers see
their active scope plus read-only ancestors needed for navigation. The
scopeRelation response field distinguishes accessible from ancestor.
The existing GET /v1/tenants/{tenantNumber}/organization-units collection
remains a flat, searchable list of every visible unit, ordered by
createdAt, organizationUnitId. The API defaults to 50 rows; the Web client
sends an explicit limit of 100 for this compatibility collection. Its cursor is
bound to the actor, current platform roles and memberships, tenant, normalized
search, status filter, and page size. A changed input or missing cursor position
returns 400 VALIDATION_FAILED. Tree navigation uses three separate cursor
collections:
GET /v1/tenants/{tenantNumber}/organization-units/rootsreturns only visible roots.GET /v1/organization-units/{organizationUnitId}/childrenreturns only the direct visible children of that unit.GET /v1/tenants/{tenantNumber}/organization-units/search?q=...searches keys and names throughout the complete actor-visible hierarchy. Every match includesvisibleAncestorPath, ordered from the visible root to its parent.
Every organization response contains hasChildren, calculated for the
caller’s visible hierarchy. Tree pages are ordered by
key, organizationUnitId. Their opaque cursors are bound to the actor, current
memberships, tenant, navigation mode, parent or search term, status filter, and
page size. A cursor cannot be reused after one of those inputs changes;
malformed, stale, or incompatible values return 400 VALIDATION_FAILED.
Setting an organization unit to disabled makes its entire subtree effectively
disabled without rewriting child status values. Only platform and tenant
administrators can reactivate disabled subtrees. Reparenting is serialized per
tenant, rejects cycles and cross-tenant parents, and requires an organization
administrator to retain administration over the complete subtree before and
after the move. Organization administrators cannot move a unit to the root.
Users and memberships
Section titled “Users and memberships”Global /v1/users routes are restricted to platform administrators. User
creation requires a temporary password, stores only an Argon2id hash, and sets
passwordChangeRequired to true. Password values are never returned. Resetting
a password revokes all sessions and cannot be used on the calling administrator
itself.
Tenant user views expose users with membership history in that tenant. They do not expose global roles, protection state, or global metadata.
GET /v1/users, GET /v1/tenants/{tenantNumber}/users, and
GET /v1/tenants/{tenantNumber}/memberships are counted cursor collections.
They default to 25 rows and accept only 25, 50, or 100. totalCount is exact
for the same filters and authorization scope as items, calculated in the
same database statement and snapshot. User pages use stable ascending
createdAt, userId order; membership pages use createdAt, membershipId.
Their opaque cursors are bound to the actor, current grants, tenant where
applicable, filters, and page size. Malformed, stale, oversized, or
incompatible cursors return 400 VALIDATION_FAILED.
Membership list items include the authorized User display name and email plus
the Organization Unit name, or null for tenant-wide grants. These labels are
part of the list projection and come from the same authorization-scoped query;
clients do not need per-row detail requests. Global roles, User metadata, and
Organization data outside the addressed Tenant are not included.
Memberships are independent grants. tenant_admin is tenant-wide,
org_admin requires an organization unit, and viewer may be tenant-wide or
organization-specific. Only a platform administrator can introduce a user with
no prior membership in a tenant. Tenant administrators can manage all current
and historical grants for known tenant users.
Platform administrator protection
Section titled “Platform administrator protection”Platform role changes use /v1/users/{userId}/platform-roles/platform_admin.
An administrator cannot revoke their own role or disable their own account, and
the last active platform administrator cannot be removed. Accounts actually
created by auth:admin are permanent break-glass accounts: their profile and
password remain editable, but they cannot be disabled, deleted, or stripped of
the platform role.
Temporary IOTA snapshot import
Section titled “Temporary IOTA snapshot import”POST /v1/tenants/{tenantNumber}/imports/iota-snapshot is a temporary,
platform-administrator-only migration operation. It accepts an allowed IOTA
HTTPS base URL plus username and password, loads every IOTA tenant visible to
that login, and creates the valid device types and devices in the active target
tenant. The request rejects query parameters and all unknown body fields.
The credentials exist only for the current request. They are not configuration,
are not persisted, and do not appear in logs, audit metadata, errors, or the
response. The allowed URL has the exact form
https://commonapi-<slug>.xaas-a0a0.cloud/api; redirects, credentials in the
URL, alternate ports, query strings, and fragments are rejected.
The operation is create-only. Existing records are never updated, reactivated,
deleted, or moved between tenants. A valid source subset commits atomically
with per-entity and summary audit events. Expected source conflicts appear in
the bounded issues response; IOTA authentication, timeout, and response
failures return a safe DEPENDENCY_FAILURE. See the
IOTA snapshot import contract
for mapping, limits, and removal requirements.
Human-readable IOTA type codes remain names; incompatible technical keys are
derived deterministically, and identical codes across source tenants merge.
IOTA asset types, asset pseudo-devices, and devices directly connected to an
asset are intentionally excluded and reported as asset_excluded.
Delete and error semantics
Section titled “Delete and error semantics”Every DELETE in this API is a soft delete that sets status to disabled and
preserves identifiers and history. Reactivation uses PATCH with status: "active". Expected duplicates, hierarchy cycles, and protection violations
return 409 CONFLICT; hidden or cross-tenant resources return 404 RESOURCE_NOT_FOUND.
See the OpenAPI reference for exact schemas and route parameters.