Tenancy and workspaces
Identifiers you will see
| Identifier | Meaning |
|---|---|
tenant_id / organization_id | Customer organization (ACS often uses organization id as tenant scope for connections) |
workspace_id | AI / ops workspace within the org |
connection_id | External SDK Connection row id |
installation_id | Managed solution installation id |
solution_id | Marketplace / package id (e.g. restaurant-pro) |
conversation_id | Chat / session |
request_id | Protocol / platform request id |
trace_id | Distributed trace (x-qefro-trace-id) |
tool_invocation_id / execution_id | Runtime execution headers (connector-manager) |
identity / person | End-user / Customer Hub person |
Where they appear
Protocol body
organization_id, conversation_id, identity, person, platform.
Headers (connector-manager egress)
Examples: x-qefro-tenant-id, x-qefro-organization-id, x-qefro-workspace-id, x-qefro-request-id, x-qefro-conversation-id, x-qefro-trace-id, tool invocation / execution / idempotency headers.
platform.*.context
When injected:
// platform.storage.context
{
tenant_id, workspace_id, installation_id, solution_id,
identity_id?, capabilities[], source?
}
Customer / marketing / organization contexts use similar tenant/workspace/installation/solution fields.
External SDK connection vs managed install
| Value | External SDK Connection | Managed Marketplace App |
|---|---|---|
| Routing key | sdk_connection_id on Business Tools; webhook URL | Installation binding (install:{solution}) |
connection_id | Yes (ACS connection) | No (install-centric) |
installation_id | Only if invoke scope provides it | Yes |
solution_id | Only if invoke scope provides it | Yes |
workspace_id | From tool invoke auth context when present | From install / tenant context |
platform.storage | Omitted unless install/solution scope exists | Present when storage-service configured |
| Domain data default | Your ERP/DB | ctx.storage collections |
Implementation detail (ACS): build_platform_storage returns None when there is no installation_id and no non-empty solution_id, so pure org SDK tools fail closed on ctx.storage.
Isolation rules
- Never trust client-supplied tenant ids without platform context.
- Scope all writes using
platform.storage.context(managed) or your own per-tenant credentials (external ERP). - Do not call other applications’ tools directly — use Organization workflows across apps.
Workspace entitlements (max_workspaces)
Workspace capacity is a platform / billing entitlement, not an application concern.
Tenant
→ Plan / subscription
→ entitlements.max_workspaces
→ Workspace Service (create check)
| Plan | max_workspaces |
|---|---|
| Trial | 1 |
| Starter | 3 |
| Pro | 10 |
| Growth | 25 |
| Enterprise | custom (workspaces_total quota) or unlimited |
Active workspaces = rows in workspaces for the tenant. Hard-deleted workspaces no longer count.
Create path: lock tenant row → count workspaces → compare to entitlement → insert (or workspace_limit_reached).
Downgrade: existing workspaces are kept; over_limit is true and new creates are blocked until count ≤ limit or the plan is upgraded.
Applications must not implement restaurant-pro.maxWorkspaces (or similar). One primary application per workspace remains a separate workspace-level rule.
Portal reads usage from GET /api/v1/tenant/billing (entitlements / usage / workspace_entitlement).
User / execution context
identity— channel-resolved attributes (phone, email, …)person— Hub Person snapshot when linkedauthentication— prior SDK auth payloadchannel— whatsapp / widget / …trace_idonctx.trace_idwhenx-qefro-trace-idis passed throughhandleRaw(defaultlistenpath does not forward headers into dispatch today — use bodyrequest_idfor correlation)