Tenant scoping and tenant switching
The logiccloud orchestrate API is multi-tenant. This page explains how a request selects its tenant, when a tenant switch is required, and how to read the audit trail. It applies to the web UI, the CLI, the SDKs and direct API calls. The server applies the same rules to all of them.
In the examples, $API is the base URL of your logiccloud orchestrate API, $TOKEN is
a bearer token and <organization-id> is the UUID of an organization. Set
$API once before you run them:
API=https://api.example.comOverview
Section titled “Overview”- Every endpoint acts in exactly one tenant (organization), except the global operations listed below.
- You select the tenant with the
x-organization-idheader. - A member of a tenant acts in it without a switch.
- A superadmin or a user with the SUPPORT system role needs an open tenant switch to act in a tenant they are not a member of.
- An API key is fixed to its own organization and never uses a switch.
The seven rules
Section titled “The seven rules”- The API is tenant-scoped. Send the
x-organization-idheader with the organization UUID. The header selects the tenant. It grants nothing: the server still checks that you may act there. A malformed value returns 400. Without the header, the server uses your open switch, or else your primary organization. A route that names an organization in its path or body uses that organization, and a conflicting header returns 403. The tenant slug is a web UI concept. The API accepts only the organization id. Thex-organization-sourceheader grants nothing; a forged value never skips a check. - Home tenants need no switch. A home tenant is any tenant you are a member
of. This holds for superadmins and for regular users.
POST /api/v1/tenant/switchto a home tenant creates no switch. It ends any open switch and returns 204. - A foreign tenant needs an open switch. A superadmin, or a user with the
SUPPORT system role, who acts in a tenant they are not a member of needs an
open switch. Open it with
POST /api/v1/tenant/switchand areasonof 3 to 500 characters. The response is 201 and the server writes an audit entry. Without a switch, the API returns 403TENANT_SWITCH_REQUIRED. This applies to the web UI and to direct API calls in the same way. - A switch has a limited lifetime. The default is 8 hours. You can set an
expiresAtup to 72 hours from now when you open it. You can extend the switch withPATCH /api/v1/tenant/switchand a reason. The new expiry is at most 72 hours from now. Each extension writes an audit entry. A switch ends at its expiry, withDELETE /api/v1/tenant/switch, or when the user logs out of the web UI. A sweep runs every 5 minutes. It closes expired switches and writes the audit entry. An expired switch stops granting access at its expiry time. - Login lands in the switch tenant. After login in the web UI, an open switch decides which tenant you land in. Without a switch, or with an expired one, you land in your primary tenant.
- Navigation by slug updates the switch. When you open a tenant by its slug
in the web UI, the web UI updates your switch record. Every change appears in
the audit log with an action and a trigger. Actions are
switch,returnandextend. The trigger of areturnisuser,home,replaced,logout,expiredorrevoked(the system role was revoked). - API keys are fixed to one organization. A key never uses a switch, not
even the switch of the user who created it. A header that names another
organization returns 403. Every
/api/v1/tenant/switch*endpoint returns 403 for an API key.
Global operations
Section titled “Global operations”A few endpoints are not scoped to one tenant and need no x-organization-id:
- the
/api/v1/tenant/*context and switch endpoints, - the platform-level organization endpoints under
/api/v1/organizations(superadmin), - the platform-wide activity and reporting endpoints under
/api/v1/activity, - authentication event ingestion.
All other endpoints are tenant-scoped. Global operations never return
TENANT_SWITCH_REQUIRED.
The 403 body
Section titled “The 403 body”A superadmin or SUPPORT user who targets a tenant they are not a member of, without an open switch, receives this response:
{ "statusCode": 403, "error": "Forbidden", "code": "TENANT_SWITCH_REQUIRED", "message": "A tenant switch is required to act in this organization", "organizationId": "<organization-id>"}Open a switch, then repeat the request. Other tenant rejections use the
standard error body with a message:
Not a member of this organization: a user without a system role targets a tenant they are not a member of.Organization not foundorOrganization is inactive.- The
x-organization-idheader and the organization in the path differ. - The caller lacks the permission in the tenant.
Use case 1: a regular user with one tenant
Section titled “Use case 1: a regular user with one tenant”A user without a system role has one tenant. The user never holds a switch. A switch to the own tenant returns 204 and creates no record. A switch into any tenant the user is not a member of returns 403, and no record is created.
curl -H "Authorization: Bearer $TOKEN" \ -H "x-organization-id: <organization-id>" \ "$API/api/v1/devices"Use case 2: a user moves between their own tenants
Section titled “Use case 2: a user moves between their own tenants”A user who is a member of tenants A and B moves between them with a home
switch. The API answers 204 and creates no switch record. If a switch was open,
the API ends it with the trigger home.
curl -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"organizationId": "<organization-id>"}' \ "$API/api/v1/tenant/switch"Use case 3: a superadmin enters a customer tenant
Section titled “Use case 3: a superadmin enters a customer tenant”A superadmin opens a switch with a reason. The API answers 201 and writes an
audit entry. The superadmin then calls the API with the customer tenant in
x-organization-id. The switch ends with DELETE /api/v1/tenant/switch, at
logout from the web UI, or at expiry. The superadmin can extend the switch
before it ends.
Open a switch with the default lifetime of 8 hours:
curl -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"organizationId": "<organization-id>", "reason": "Ticket 1234: investigate failed deployment"}' \ "$API/api/v1/tenant/switch"Open a switch with a chosen expiry. expiresAt is at least 1 minute and at most
72 hours from now. Do not send duration together with expiresAt.
curl -X POST -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"organizationId": "<organization-id>", "reason": "Planned maintenance", "expiresAt": "<ISO-8601-time>"}' \ "$API/api/v1/tenant/switch"Work inside the tenant:
curl -H "Authorization: Bearer $TOKEN" \ -H "x-organization-id: <organization-id>" \ "$API/api/v1/devices"Extend the open switch. The API returns 404 when no switch is open and 409 when the switch has already expired:
curl -X PATCH -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"reason": "Work takes longer than planned", "expiresAt": "<ISO-8601-time>"}' \ "$API/api/v1/tenant/switch"End the switch. The API returns 400 when no switch is open:
curl -X DELETE -H "Authorization: Bearer $TOKEN" "$API/api/v1/tenant/switch"A switch applies to the requests of its own user only. A new switch into another tenant replaces the open one, so a user has at most one open switch.
Use case 4: a superadmin calls a tenant without a switch
Section titled “Use case 4: a superadmin calls a tenant without a switch”A superadmin sends a request for a tenant they are not a member of, and no switch is open. The API rejects the request with the 403 body shown above. This is the same for the web UI and for direct API calls. A hidden button is not access control: the server decides.
curl -i -H "Authorization: Bearer $TOKEN" \ -H "x-organization-id: <organization-id>" \ "$API/api/v1/devices"The same rule holds for a user with the SUPPORT system role.
Use case 5: login with an open or an expired switch
Section titled “Use case 5: login with an open or an expired switch”After login, the web UI asks the API for the tenant context. An open switch
decides the tenant you land in. Without a switch, you land in your primary
tenant. A switch that has expired is closed with an audit entry (trigger
expired), and you land in your primary tenant.
Use case 6: CLI or SDK with an API key
Section titled “Use case 6: CLI or SDK with an API key”An API key is fixed to the organization it was issued for. The key works with no
x-organization-id header or with its own organization id. A header that names
another organization returns 403. The key never uses a switch, not even the
switch of its creator. All /api/v1/tenant/switch* endpoints return 403 for a
key. The same holds for the other /api/v1/tenant context endpoints.
curl -H "X-API-Key: $API_KEY" "$API/api/v1/devices"
curl -i -H "X-API-Key: $API_KEY" \ -H "x-organization-id: <other-organization-id>" \ "$API/api/v1/devices"The second call returns 403.
Use case 7: read the audit trail for tenant switches
Section titled “Use case 7: read the audit trail for tenant switches”Every switch event writes one audit entry with the entity type tenant-switch.
The metadata of an entry holds the action (switch, return or extend),
the reason, the tenant names and, for a return, the trigger. The entry
for a closed switch is filed under the tenant the switch entered. The audit
endpoints are tenant-scoped, so a superadmin needs an open switch to read the
audit log of a tenant they are not a member of. The caller needs the audit read
permission.
List all tenant switch entries of a tenant:
curl -H "Authorization: Bearer $TOKEN" \ -H "x-organization-id: <organization-id>" \ "$API/api/v1/audit?entityType=tenant-switch"Read the entries of one switch. <switch-id> is the id returned when you
opened the switch:
curl -H "Authorization: Bearer $TOKEN" \ -H "x-organization-id: <organization-id>" \ "$API/api/v1/audit/entity/tenant-switch/<switch-id>"Export the entries as CSV. The last two columns are Reason and Metadata:
curl -H "Authorization: Bearer $TOKEN" \ -H "x-organization-id: <organization-id>" \ "$API/api/v1/audit/export?entityType=tenant-switch"Two more endpoints help with switches. GET /api/v1/tenant/switch/history
lists your own switches. GET /api/v1/tenant/switches/active lists all open
switches (superadmin and SUPPORT only). See Audit Logs
for the web UI.
Limits
Section titled “Limits”- Logout ends a switch only through the web UI. The web UI calls
DELETE /api/v1/tenant/switch?trigger=logoutwhen you sign out. A CLI or SDK client does not sign out through the web UI. For these clients the expiry is the backstop. End the switch yourself withDELETE /api/v1/tenant/switchwhen your work is done. - A WebSocket stays with the tenant it was opened for. A realtime connection that you opened before a switch ended keeps streaming events of its tenant until it reconnects. Reconnect after you end or change a switch.
- The
organization_idclaim of the access token can be stale. The token keeps the organization list it had at sign-in, and a token refresh does not reorder it. The web UI uses the claim only for a call without a tenant slug. The server checks your membership or your open switch on every call, so a stale claim never grants access to a tenant. - A custom expiry and an extension allow at most 72 hours from now. A later time returns 400. Extend again before the switch ends.
- A reason has 3 to 500 characters after trimming. A shorter or longer reason returns 400.