Skip to content
SENS Platform Docs

ADR-0011 Public Tenant Numbers and Hybrid Resource Routes

Accepted on 2026-08-10.

Tenant UUIDs were exposed in every tenant-owned API path. This made browser and operator-facing tenant selection needlessly opaque and repeated the Tenant identifier even after a globally unique aggregate ID was known. Deriving Tenant scope from a header or UI session would shorten URLs, but would also make the authorization boundary implicit and create ambient request state.

REST does not prescribe one URL hierarchy. Hierarchical resource names are useful for collection selection and creation, while globally unique aggregate IDs allow a shallower canonical member URL.

TenantId remains the internal UUID used by tables, foreign keys, repository methods, events, audits, and authorization. TenantNumber is a positive, immutable, globally unique int32 generated by PostgreSQL and exposed for Tenant selection.

The API uses this hybrid rule:

  • Tenant collections, creates, projections, and singletons use /v1/tenants/{tenantNumber}/….
  • A globally unique aggregate member uses a flat canonical route, for example /v1/devices/{deviceId}, /v1/memberships/{membershipId}, or /v1/flows/{flowId}/versions.
  • Dependent resources stay below their aggregate root. Globally unique child IDs do not automatically receive additional top-level endpoints.
  • The Tenant is never inferred from a request header or UI session.
  • Removed tenant-nested member routes have no aliases and return 404.

A typed resource locator resolves the internal Tenant UUID before authorization on flat routes. It has one fixed method per aggregate and accepts no dynamic table names. Services and repositories still require both the resolved Tenant UUID and the resource ID, so mutations remain tenant-scoped inside their transaction. Raw-ingestion replay resolves the composite key consisting of the Raw ID and receivedAt.

The Web Console deliberately keeps a visible numeric workspace path such as /tenants/17/devices/{deviceId}. It uses the number for navigation and API collections, but the internal UUID for local permission evaluation.

Tenant numbers are discoverable labels, not authorization evidence. A user with access to several Tenants can submit a flat mutation for a resource in the wrong—but still authorized—Tenant if they confuse aggregate IDs. We accept this trade-off rather than requiring a redundant Tenant header. The UI preserves a visible Tenant workspace to reduce this risk.

Unknown or inaccessible flat resources both return 404. A known Tenant for which the actor lacks the required role keeps the existing 403 behavior. Invalid numeric Tenant syntax returns 400 after authentication. Tenant numbers must not become metrics labels.

The API change is breaking: clients must coordinate their rollout and there is no compatibility alias. After numeric routes are released, production recovery is a forward fix; rolling the database column down would invalidate the public contract. The expected platform release impact is major; this decision does not itself authorize or perform a version bump.

  • Keep UUIDs public everywhere: secure but unnecessarily opaque and repetitive.
  • Use only flat collections: loses explicit Tenant selection for list and create operations.
  • Use X-Tenant-ID or session state: hides security-relevant scope in ambient state and makes links less self-describing.
  • Replace internal UUID foreign keys with numbers: creates a large, risky data migration without improving isolation or repository contracts.

This decision is consistent with Fielding’s REST architectural style, Google AIP-122, and the Microsoft Azure REST API Guidelines.