Skip to main content

Tenant Topology API

GET /api/tenant-topology tells an application which of the signed-in user's allowed tenants lie below the application's root tenant, how deep each one is, and whether it has children of its own.

It exists because an application must not learn its tenants by calling the GraphQL or REST routes of other tenants with the user's token: the tenant gate of the platform services refuses such calls when it is enforced. Identity answers the question from its own tenant registry instead. For the application-side view see Which tenants may my app offer?. Angular applications do not call the endpoint themselves; they use TenantTopologyService of @meshmakers/shared-auth.

Request​

GET https://<identity-host>/api/tenant-topology?scope=<rootTenantId>
Authorization: Bearer <user access token>
PartDescription
<identity-host>The issuer of the user token (the Identity Service). The route has no {tenantId} segment.
scope (query, required)The application's root tenant, for example acme-root.
Bearer tokenThe access token of a signed-in user. The token needs the octo_api (or octo_api.read_only) scope; no role is required.

Only user tokens are accepted. A token issued by the client-credentials flow has no user and therefore no allowed tenants; it is refused with 403.

Response​

200 OK, application/json:

{
"scope": "acme-root",
"scopeHasChildren": true,
"tenants": [
{ "tenantId": "acme-plant-a", "depth": 1, "hasChildren": false },
{ "tenantId": "acme-group-b", "depth": 1, "hasChildren": true },
{ "tenantId": "acme-plant-b1", "depth": 2, "hasChildren": false }
]
}
FieldTypeDescription
scopestringThe scope as requested (trimmed).
scopeHasChildrenbooleanThe root tenant has at least one tenant below it. false together with an empty tenants list is the legacy single-tenant case: the root tenant is the application tenant.
tenantsarrayThe tenants the user may use below the scope. Never contains the scope itself.
tenants[].tenantIdstringId of the tenant.
tenants[].depthintegerDistance from the scope: 1 is a direct child of the scope, 2 a child of that child, and so on.
tenants[].hasChildrenbooleanThe tenant itself has at least one child. The children are not listed unless the user is allowed to use them.

Nothing else is ever returned: no scope entry, no tenant outside the user's allowed tenants or outside the subtree of the scope, no parent ids and no tenant data.

Who sees what​

The list is the intersection of two sets:

  1. the tenants named in the allowed_tenants claim of the user token, and
  2. the strict descendants of scope in the tenant hierarchy.

The claim only limits which facts are revealed (a depth and a flag about tenants the user can already sign in to). It is a selection hint, not an authorization: every request to a tenant is still checked by that tenant's service.

scopeHasChildren is only reported truthfully if the scope is in the user's allowed_tenants or the list is not empty; otherwise it is false. A scope that does not exist, is not a well-formed tenant id, has no tenants the user may use, or belongs to someone else's hierarchy all answer the same 200 with an empty list and scopeHasChildren: false, so the endpoint reveals nothing about tenants the caller has no business with.

Status codes​

StatusMeaning
200Topology returned (possibly empty, see above).
400The query parameter scope is missing or empty.
401No token, or the token is invalid or expired.
403The token is a client-credentials token (no user), or the user token lacks the octo_api read scope.
429Rate limit exceeded for this user. The response carries Retry-After: 60.
503The tenant hierarchy could not be read completely. The endpoint fails closed: it never returns a partial list. Retry later.
Older identity services

An Identity Service that does not have the endpoint yet does not answer 404 for /api/tenant-topology: it answers 200 with text/html (its single-page-application fallback). Clients must treat a successful response whose content type is not JSON like a 404: the topology is unknown. TenantTopologyService of @meshmakers/shared-auth does this.

Caching and limits​

  • The topology of a scope is cached by identity for about 45 seconds. A tenant that was just created or moved can therefore take up to about 45 seconds to appear or disappear.
  • The rate limit is applied per user (sliding window of one minute, 60 requests by default). Cache the result in the application instead of calling the endpoint on every page view.
  • The endpoint can be called from a browser: the CORS policy of identity allows the origins that are registered on the clients of the tenants.

Not a security boundary​

The topology only decides what an application offers in its tenant selection. It does not grant access and does not replace any check: the backend services enforce the tenant of the token and the user's permissions on every request. An application must not treat a tenant in the list as proof of access, nor a missing tenant as a reason to skip a check elsewhere.