Cross-Account Access and Resource Sharing
Overview
ChatBotKit accounts are isolated by default. A bot in one sub-account cannot see a dataset in another. That isolation is the right starting point - it keeps tenants safe from each other, keeps environments from contaminating each other, and keeps experimental work out of production.
The default is not the only option. The platform supports controlled cross-account access through a per-resource visibility setting. A dataset that should be reusable across many tenant accounts can be marked as such; a curated set of bots that should be available to everyone on the platform can be exposed publicly; an internal "platform" account can publish skillsets that all of its sister accounts consume.
This guide covers how that works, when to reach for it, and what the trade-offs are. It is the runtime-architecture companion to the Agent Deployment Guide, which covers how configuration lands inside a single account in the first place. The deployment guide is mostly about pipelines and environments; this guide is mostly about how data and capabilities flow between accounts once they exist.
If your design only needs strict isolation between accounts, you can read the first two sections of this guide and stop. The rest is about the architectures that become possible when controlled sharing is in scope.
The Visibility Model
Every shareable resource on the platform carries a visibility property with three possible values: private, protected, and public. Visibility is set at the resource level, individually, and can be changed at any time (subject to the team permissions on the owning account).
Private (the default)
A private resource is visible only to the account that owns it. A bot in account A's sub-account cannot read it. A different sub-account belonging to the same master account cannot read it. The dashboard, the API, search, listing - none of them surface a private resource outside the account boundary.
This is the safe default. Unless you have an explicit reason to share, leave resources private. Most resources, in most deployments, should stay this way for their entire lifecycle.
Protected
A protected resource is visible only to related accounts in the same master account and sub-account family, and only when the consumer holds a direct reference to it. There is no discovery - protected resources do not appear in another account's listing, search, or dashboard. A consumer needs to know the resource's ID or use the supported alias reference pattern up-front, typically because it was shared through deployment configuration, tenant onboarding, or some other controlled handoff.
protected is the right default for "shared but not advertised" inside one related-account family - a dataset that many tenant accounts read from, a skillset that the platform team maintains and the product team consumes, an MCP server hosted by one account and called by its sibling or child accounts. The resource lives in one account, but a known set of related accounts can reference it.
Public
A public resource can be referenced by any account on the platform, without the consumer needing a prior arrangement with the owner. Unlike protected, where the consumer typically holds the ID because of some out-of-band exchange (a partner agreement, a tenant onboarding step), public removes that implicit "you got the ID because we agreed you should" assumption - anyone with the ID can use the resource.
Importantly, public resources are not exposed on platform discovery surfaces. They do not appear in cross-account search, listings, or galleries. The way another account ends up referencing a public resource is the same as for protected: someone passes the ID, through a documentation page, a tutorial, a marketplace entry, or a configuration value. The difference is the implicit access policy attached to the resource, not the discovery mechanism.
public is the right setting for resources that are genuinely intended for unrestricted use by anyone who learns about them - a reference blueprint that demonstrates a pattern, a curated dataset offered as community material, a bot intended as a starting template for others to fork. It is rarely the right choice for production tenant infrastructure: if it would be a problem for an arbitrary account that happens to obtain the ID to read or use the resource, it is not a candidate for public.
Not every resource type supports every visibility
The three visibility values are intentionally consistent across resource kinds, so the mental model stays uniform. Within that, some resource types do not support public access at all. This is enforced at the API level - a request to set visibility: public on an unsupported kind is rejected. The types that allow public are the ones where unrestricted use makes sense (blueprints, reference datasets, and similar share-friendly resources); types that intrinsically encode tenant-specific or operational context are capped at protected. If you need to share a resource of one of those types, protected plus an out-of-band ID share is the path; there is no public escape hatch.
Visibility is per-resource, not per-account
There is no account-level "make everything public" switch. Visibility is decided one resource at a time, which means the consequences of getting it wrong are also one resource at a time. A misconfigured public setting on a single dataset does not expose the rest of the account.
Conversely, sharing an account's resources with another account is not done by relaxing the account boundary. It is done by marking the specific resources you intend to share, and leaving everything else private. The account boundary itself never weakens.
What protected and public actually permit
It is worth being explicit about what visibility lets the consumer do.
- Read access. A consuming account can fetch the resource and use it when the visibility rule allows it (a related account can read a
protecteddataset, and any account that obtains the reference can use apublicresource). - No write access. Visibility does not grant write or delete to the consumer. Only the owning account can modify or delete the resource. A change made by the owner is observed by all consumers immediately on the next read.
- No transfer of billing. Usage and storage of the resource itself are billed to the owning account. Any work performed by a consumer that uses the resource (running a bot, querying a dataset) is billed to whoever runs the work, not to the owner of the data.
This split - owner controls the resource, consumer pays for use - is what makes shared-platform architectures viable. The owning account can publish a high-quality dataset without worrying about consumers running up its bill, and consumers can use it without needing write access to anything they do not own.
How Cross-Account Access Actually Works
Once a resource is protected or public, a different account can reference it if the visibility rule and account relationship allow it. The mechanics are deliberately simple: the consumer references the resource either by its globally unique ID or, within the same master account and sub-account family, by the supported alias reference forms. The platform resolves the reference, checks visibility, and either grants the read or rejects the call.
Referencing by ID
A bot in account B that wants to use a dataset owned by account A sets its dataset_id to the dataset's ID. The fact that the ID belongs to a different account is invisible to the API call - there is no separate "external dataset" field, no namespace prefix, no special authentication step. The platform sees the ID, looks up the resource, and applies visibility rules.
This is why IDs being globally unique matters. Every resource on the platform has an unambiguous identifier, and that identifier works the same whether the consumer is the owner or not.
Aliases are scoped, not global
Aliases are still scoped to ownership context. An alias support-bot in account A and an alias support-bot in account B are unrelated by default, and there is no platform-wide alias namespace. What changes inside a shared master account and sub-account structure is that the platform supports explicit alias reference patterns that tell it which account scope to resolve against.
The three patterns to know are:
@aliasfor a resource in the current account@@aliasfor a protected resource in the master account context@user-alias@aliasfor a protected resource in a sibling sub-account, whereuser-aliasis the sibling sub-account alias andaliasis the resource alias inside that account
In other words, aliases do not cross account boundaries implicitly, but they can cross them explicitly within the same master account and sub-account family when you use the alias reference pattern.
This matters operationally because it gives you a stable naming contract for shared resources. A tenant can reference @@shared-knowledge instead of baking a raw dataset ID into configuration, and can reference @operations@handoff-bot when a sibling account owns the bot being shared. Outside that family boundary, or where you want an account-agnostic contract, the globally unique ID is still the universal option.
A common pattern is to capture these cross-account references as platform-level constants in your deployment configuration: a Terraform variable, a CLI solution input, or an environment variable. In some deployments that constant is a raw ID. In others it is an alias pattern such as @@shared-knowledge or @operations@handoff-bot. Either way, the deployment layer stays responsible for deciding which shared resource a tenant should bind to.
What happens when the owner changes the resource
A read against a protected or public resource always reflects the current state in the owning account. There is no snapshot, no copy, no caching layer that consumers can rely on. If the owner edits the dataset, every consumer sees the new version on the next read.
This has implications for change management:
- A change to a shared resource is, in effect, a change to every consumer simultaneously. There is no per-consumer staging.
- Rollback of a shared resource through the audit log applies globally; you cannot roll back for one consumer and not another.
- Removing a shared resource (deleting it, or making it
private) breaks every consumer that references it, immediately.
The practical takeaway: shared resources have outsized blast radius compared to private ones. Treat changes to them with the same discipline you would apply to a shared library or a public API - versioned releases where appropriate, deprecation periods, communication with consumers.
What happens when the consumer is removed
The flip side is symmetric. If a consuming account is deleted, its references vanish but the shared resource is unaffected. The owner does not need to do anything. Consumers come and go without disturbing the resource itself.
Patterns for Shared Infrastructure
With the visibility model in mind, several useful patterns become possible. These are the shapes worth knowing because they correspond to architectural choices most teams need to make at some point.
The platform-account pattern
A single account - often the master account itself, or a dedicated "platform" sub-account - owns a curated set of protected resources that other accounts in the same organisation consume. Knowledge bases, skillsets representing internal tools, MCP servers exposing internal APIs, even reference bots used as templates.
This is the right shape when:
- Multiple tenants need access to the same content or capability, and you do not want to maintain N copies of it.
- The shared content is updated centrally and consumers should pick up updates automatically.
- Ownership of the shared resource sits with a single team that wants explicit control over its lifecycle.
The platform account is a normal account - it has its own team (usually small and tightly controlled), its own deployment pipeline, and its own audit log. The only thing that distinguishes it is that its resources are protected and that other accounts have been given the IDs.
The external distribution pattern
For accounts that are not in the same master account and sub-account family, protected is the wrong model. Protected sharing relies on the related-account relationship. An unrelated vendor, partner, or third-party publisher cannot expose a resource to your account that way.
If the goal is cross-organisation reuse, the resource has to be public and the resource type has to support public visibility. In that shape, the external party publishes a resource intended for reuse, your account references it by ID, and the access policy is deliberately open to any account that obtains that ID.
This is workable for ecosystem material, reference implementations, and other intentionally reusable assets. It is usually a poor fit for private partner integrations or customer-specific infrastructure, because public does not express "shared only with this one outside organisation". If that narrower policy is required, the safer pattern is to duplicate the resource into your own account boundary instead of referencing the external one directly.
The white-label / base-configuration pattern
A SaaS product where every customer gets a similar agent, but each customer's tenant account adds its own customisations on top.
The base configuration lives in a platform account and is protected. Each customer's tenant sub-account references the base resources directly (cheapest, most consistent) or clones them on creation and customises locally (more flexibility, more drift).
The trade-off here is between consistency and customisation. Referencing the shared base means every customer benefits when you improve it, but customers cannot diverge. Cloning the base on tenant creation means each customer can diverge, but improvements to the base do not flow to existing tenants without an explicit migration step.
A useful middle ground is shared infrastructure plus per-tenant customisation: the dataset and skillset are referenced from the base, the bot is per-tenant (so it can have a customised backstory and per-customer integrations) and points at the shared dataset and skillset by ID.
The marketplace pattern
A more open variant of the platform-account pattern, using public resources rather than protected. The owning account publishes resources intended for unrestricted use - community blueprints, reference datasets, example bots - and any account on the platform that obtains the ID can use them. Discovery happens through your own channels (documentation, tutorials, a marketplace listing, a community forum), not through a platform-wide discovery surface.
This is rarely the right shape for tenant infrastructure (the consumer policy is wrong for that - anyone with the ID can use it, with no implicit "we agreed you should have this" step) but is useful for ecosystem material: education, examples, templates that you want anyone to be able to fork or reference.
The cross-environment shared-resource pattern
A more subtle variant. A team running multiple environments (dev, staging, prod) sometimes wants a single canonical resource - typically a large, slow-to-build dataset - shared across all three rather than re-built per environment.
This works only when those environments are modelled as related accounts inside the same master account and sub-account family. In that shape, you could mark the canonical dataset as protected in one account and reference it from the others. The trade-off is that any change to the dataset is immediately visible in every environment that consumes it, which defeats the whole point of having staging to test changes in isolation.
A safer shape: keep dev and staging on a separate, dev-owned dataset that they can mutate freely, and only point production at the production-owned dataset. The promotion of dataset content from staging to production becomes its own deliberate workflow, rather than something that happens implicitly through a shared reference.
Portal-Based Cross-Account UX
The visibility model covers what resources are visible to other accounts. A separate question is what humans can see across accounts.
For the operator population, the team is the boundary, and a user who is on multiple teams can switch between them in the dashboard. That is the unrestricted form: full operator access to each account they belong to.
For the wider organisation, portals can be configured to expose specific applications inside an account to a defined set of users. A portal might give a QA team read access to the Inbox application against a production account, without giving them anything else.
Portals are scoped to a single account. They are not the right tool for unifying access across accounts: a QA reviewer who needs to see conversations from five tenant accounts will need either five separate portals (one per tenant), or - depending on your architecture - a single portal against a parent account whose resources are joined back from the tenants.
If the use case is a unified operator surface across accounts, the right primitive is the master credential plus the run-as mechanism, used by tooling that aggregates reads across accounts. If the use case is end-user access (i.e. real humans who are not operators), portals per account remain the cleanest answer, even at the cost of multiplicity.
Trade-Offs
Cross-account sharing is a powerful tool, and like most powerful tools it adds new failure modes. The trade-offs worth weighing before adopting any of the patterns above:
Blast radius
A protected resource consumed by N tenants is a single point of failure for all of them. A breaking change to that resource breaks N tenants at once. A misconfiguration that flips it to private breaks them just as fast. The blast radius of a private resource is one account; the blast radius of a heavily-shared resource is every account that consumes it.
The mitigation is the same as for any other shared dependency: tighter change control, smaller commits, awareness that the audience for a change is broader than it looks. Treat changes to shared resources the way you would treat changes to a public API.
Rollback complexity
Rolling back a private resource is simple - only the owning account is affected. Rolling back a shared resource through the audit log applies globally to every consumer at once, whether or not each consumer was affected by whatever the rollback is fixing. There is no way to roll back the resource for some consumers and not others.
If you need per-consumer rollback semantics, the resource cannot be shared; it has to be cloned per consumer. This is one of the strongest reasons to choose the per-tenant clone variant of the white-label pattern over the shared-by-reference variant.
Usage attribution
When a consuming account uses a shared resource, the cost of that use is billed to the consumer, not the owner. This is usually desirable. It does mean that the owner of a heavily-used protected dataset cannot tell, from billing alone, who is using it or how much. If attribution matters (for capacity planning, internal chargeback, or telling whether a tenant is hammering a shared service), you need explicit instrumentation; the platform does not synthesise it from the visibility model.
public removes the implicit access policy
Setting a resource to public means any account that obtains the ID can use it without a prior arrangement. This is a deliberate property of public, but it is occasionally surprising - a resource that you marked public so that one specific external account could use it is also usable by every other account that ends up with the ID. If that is not what you want, the right answer depends on the relationship: use protected for related accounts in the same master account and sub-account family, or duplicate the resource into the consumer's own account when the consumer is external.
Coupling between accounts
A consumer that depends on a shared resource owned by another account is coupled to the owner's lifecycle. If the owning account is deleted, sub-accounts are reorganised, or visibility is changed, the consumer breaks. This is straightforward when the owner and consumer are operated by the same team. It is more delicate when the owner is external, because the viable model there is usually public rather than protected. In those arrangements, treat the resource ID as part of an interface contract that should not change without coordination.
Cross-account audit story
Each account's audit log captures changes to its own resources. A consumer reading a shared resource sees the current state but does not see the history of how it got there unless it has access to the owning account's audit log. For regulated or post-incident-review use cases, this matters: "who changed the shared dataset, and when" is answerable by the owner but not by the consumer, and exporting the relevant audit log slices to interested consumers is a workflow you may need to build.
When Not to Share
Sharing is a tool. Not every multi-account architecture should reach for it.
The cases where keeping resources private and accepting some duplication is the better answer:
- Strict tenant isolation requirements. If your compliance or security model requires that no resource is shared between tenants, even by reference, then the visibility model is not your friend; per-tenant clones are.
- Heavy per-tenant customisation. If every tenant needs to mutate the resource locally (to add their own knowledge to the dataset, their own abilities to the skillset, their own backstory to the bot), there is no shared baseline to reference - each tenant ends up with its own resource anyway, so cloning at provisioning time is cleaner than referencing-then-cloning later.
- Independent rollback per tenant. As above, shared resources cannot be rolled back per consumer. If your operational model requires that one tenant's incident is fixable without touching another tenant, the resource cannot be shared.
- Performance-critical paths where consistency over time matters. A model that has been validated against a specific dataset version may misbehave if that dataset changes underneath it. Shared-by-reference resources change live; a per-tenant clone is pinned until you choose to update it.
The right way to think about it: sharing is excellent for infrastructure-shaped resources (long-lived, centrally maintained, broadly applicable) and risky for product-shaped resources (per-customer, mutable, business-critical). When in doubt, keep private and revisit only if duplication becomes a real cost.
A Worked Example: Internal Platform with Tenant Sub-Accounts
Putting it together. Suppose you operate a SaaS product where each customer is a sub-account, and your product is built on a small set of shared capabilities that should not be re-implemented per tenant.
Account topology
- One platform sub-account (or a separate platform master account, depending on your topology - see the deployment guide). It holds the shared resources.
- Many tenant sub-accounts, one per customer.
What lives in the platform account
- A
protecteddataset calledproduct-knowledge, holding documentation and FAQs that all tenants reference. - A
protectedskillset calledcore-tools, with abilities for web search, database lookup, and a couple of internal API calls. - A
protectedMCP server integration that wraps your internal services.
These are maintained by a small platform team. Changes go through code review, are deployed via Terraform or CLI solutions, and are versioned with git tags.
What lives in each tenant account
- A
privatebot per tenant, with a tenant-specific name, backstory, and branding. - The bot's
dataset_idpoints at the platform account'sproduct-knowledgedataset (by ID, captured as a deployment input). - The bot's
skillset_idpoints at the platform account'score-toolsskillset. - A
privatedataset per tenant, holding their own customer-specific knowledge, layered on top of the shared one through skillset abilities or per-bot configuration. - A
privateSlack integration installed against the customer's Slack workspace via OAuth.
Why this shape
- Shared resources are referenced, not cloned. When the platform team improves
product-knowledge, every tenant benefits without a per-tenant migration. - Tenant-specific resources are private, owned by the tenant account. A change in tenant A's dataset has no effect on tenant B.
- The bot is per-tenant, which is what allows tenant A and tenant B to have different names and personalities while still using the same underlying tools and knowledge.
- The audit log for the shared resources is held by the platform account; the audit log for each tenant's customisations is held by the tenant. Each story is locally answerable.
Trade-offs accepted
- A breaking change to
product-knowledgeaffects every tenant simultaneously. The platform team commits to a deprecation policy and stages changes as if the dataset were a public API. - Tenants cannot roll back the shared dataset for themselves; only the platform team can. Tenant-level rollbacks therefore only ever apply to the tenant's
privateresources. - Per-tenant usage of the shared resources is not visible from the platform account's billing. If the platform team wants to know which tenants are hitting the dataset hardest, that has to be measured explicitly.
This is one specific shape; many variants are reasonable. The point is that the choice of which resources are shared and which are per-tenant is a deliberate architectural decision, with consequences in operations, change management, and billing.
Common Pitfalls
A few patterns that tend to cause problems.
Treating public as "shared with my friends". public means anyone who obtains the ID. The platform does not advertise the resource on discovery surfaces, but anyone who learns the ID through any channel - documentation, tutorials, a leaked configuration file - can use it. If the audience is a known set of related accounts, use protected. If the audience is a known set of unrelated accounts, public is still too broad and duplication is safer.
Sharing a resource across environments to avoid duplication. Tempting, but it removes the entire purpose of having staging - changes to the shared resource are live in production immediately. The duplication is not waste; it is the buffer that gives staging meaning.
Letting cross-account IDs leak into application code. IDs that come from another account are platform-level configuration, not business logic. Capture them at the deployment layer (variables, solution inputs, environment) so that "which shared dataset" is a single thing to change.
Not communicating changes to a shared resource. A change made by the owning team is felt by every consumer instantly. If consumers do not know it is coming, the change feels like an outage. Treat shared-resource releases the way you would treat library or API releases: notify, version, deprecate.
Assuming visibility is a security boundary. Visibility is an access boundary; it is not a secrecy boundary. If a resource contains material that must not be exposed under any circumstances, it should be private and access to the owning account should be tightly controlled, not exposed via protected. Once a resource is protected, any related account in that master account and sub-account family that has the reference can read it.
Cloning when you should be sharing, or vice versa. This is the largest design mistake. Cloning produces drift - N copies of a resource that should have been one - and turns updates into per-tenant migrations. Sharing produces coupling - every change is global - and removes per-tenant rollback. Decide deliberately, on a per-resource basis, with eyes open.
Recommended Default
For a team that is just adopting cross-account sharing for the first time:
- Start with everything
private. Default to copying configuration into each account through the deployment pipeline. - Promote individual resources to
protectedonly when duplication becomes a measurable cost (large datasets, frequently-updated knowledge, expensive integration setups). - Reserve
publicfor genuine ecosystem material - templates, examples, community resources - not for tenant infrastructure. - Treat the platform account that owns shared resources as a small, tightly-controlled account with a deprecation policy on its public-facing IDs.
- Keep cross-account IDs in deployment configuration, not in application code.
Adopt sharing in the smallest possible doses. Each protected resource is a coupling point that did not exist before. The benefit is real, but only when the alternative - duplication - is genuinely costing you something. If you are not sure, leave it private.
Summary
The platform's cross-account sharing model is built on a single per-resource property: visibility. private is the default and the right answer for most resources. protected enables controlled sharing inside a related master account and sub-account family, typically with consumers that know the resource's ID or alias reference pattern through deployment configuration. public allows any account that obtains the ID to use the resource, without a prior arrangement; it is not exposed on platform discovery surfaces, and not every resource type supports it.
These three settings make a small number of architectural patterns possible: a platform account that publishes shared infrastructure to related tenants, external distribution patterns built on public resources, white-label products that combine shared bases with per-tenant customisation, and marketplace-style ecosystems built on public resources.
The trade-offs that come with sharing - blast radius, rollback semantics, usage attribution, owner/consumer coupling - are the same trade-offs that come with any shared dependency in any other system. Treat shared resources the way you would treat shared libraries or public APIs: with explicit ownership, deliberate change control, and clear communication with consumers.
For deployment and environment design (which accounts exist, how they are organised, how configuration lands inside them), see the Agent Deployment Guide. This guide picks up where that one leaves off, covering how data and capabilities flow between accounts at runtime once the topology is in place.