Skip to content
ConduitConduit

Subtenant model

Conduit is multi-tenant in two distinct senses. Org-level isolation keeps one organization’s data separate from another (covered in Security § Multi-Tenant Isolation). The subtenant model describes how organizations can be arranged in a parent-child hierarchy — typically a reseller MSP at the top, customer organizations underneath, and (optionally) sub-customer organizations underneath those.

This page covers the subtenant hierarchy itself: who can see and administer what, how vendor credentials flow downstream, and how reseller-level billing is scoped.


Conduit organizations form a tree, bounded at depth 3:

Reseller (depth 0, e.g. an MSP)
└── Customer (depth 1, the MSP's end-customer org)
└── Sub-customer (depth 2, e.g. a customer's separately-billable subsidiary)

A worked example:

WYRE (reseller)
└── Acme Manufacturing (customer)
└── Acme Internal IT (sub-customer)

Depth is enforced by a Postgres trigger. Attempting to create a fourth-tier child raises an error at the database layer.

The parent-child relationship lives on organizations.parent_org_id (a self-referencing foreign key). A NULL parent indicates a top-level org — typically a reseller, occasionally a standalone customer that does not sit under one.


Conduit has two disjoint role systems that often confuse first-time integrators:

These are the standard tenant roles that exist on every organization:

  • owner — full administrative authority over the org
  • admin — manage members, credentials, and tool allowlists
  • member — use vendor tools according to the allowlist

Org-member roles are defined per-org. A user can hold an owner role on Org A and a member role on Org B.

These are an additional, separate role space that applies only to reseller organizations:

  • reseller_owner — top-level authority over the reseller’s customer estate
  • reseller_admin — administer customers and their sub-customers (transitively)
  • reseller_billing_viewer — read-only access to billing data across the reseller’s estate
  • reseller_support_agent — temporary act-as access, granted via support grants (see below)

Reseller roles surface in the codebase as the ResellerRole type. They are disjoint from org-member roles — being a reseller_admin on the WYRE reseller does not automatically make you an admin on Acme Manufacturing’s org. What it does give you is transitive administrative authority down through the hierarchy, described below.


A reseller administrator needs to operate on customers and (often) on those customers’ sub-customers. Conduit expresses this through a recursive ancestor lookup:

For any target org, the function conduit_is_reseller_admin_of_ancestor(user_id, target_org_id) walks the parent_org_id chain bounded at depth 3 and returns true if the calling user is a reseller_owner or reseller_admin of any ancestor reseller in the chain.

Worked through the WYRE example above:

Calling user Target org Result Why
reseller_admin on WYRE WYRE true self
reseller_admin on WYRE Acme Manufacturing true Acme’s parent (WYRE) is a reseller this user administers
reseller_admin on WYRE Acme Internal IT true walks parent chain → Acme → WYRE
customer admin on Acme Acme Internal IT true via customer-admin RLS direct parent admin (separate code path)
customer admin on Acme WYRE false customer admin does not reach upstream
reseller_admin on a different reseller Acme Manufacturing false wrong ancestor chain

The depth bound is intentionally locked to mirror the hierarchy trigger’s own bound — if the hierarchy ever expands, both must move in lock-step.

A simpler, single-level helper exists for cases that only need direct-parent checks: conduit_is_reseller_admin_of_parent. Use the recursive _of_ancestor form when the call site needs to reach sub-customers.


A reseller can share vendor credentials downstream to one or more customers, so an MSP doesn’t have to re-collect credentials for tools its end-customers will use through the reseller’s contract. This is modelled as a reseller-shared vendor grant.

When Conduit resolves a credential for a tool call, the credential resolution hierarchy is:

  1. Personal credential (the user’s own)
  2. Team credential (single matching team)
  3. Organization credential — first the customer org’s own credential, then a reseller-shared grant pointing at the reseller’s credential

When step 3(b) is used — the call resolves through a reseller-shared grant rather than the customer’s own credential — Conduit records this in the audit log so the resolution path is observable.

Two operational notes:

  • Sub-customers do not inherit their parent customer’s credentials automatically. Each org’s resolution looks at its own credentials first, then walks up to a reseller-shared grant if one is configured for that vendor and that customer. There is no implicit walk through intermediate customer orgs.
  • Reseller support agents (reseller_support_agent role) can act on behalf of customers via short-lived grants. These grants are time-bounded and audit-logged separately.

A reseller can configure markup pricing for each of its customers (and, where applicable, their sub-customers) in reseller_pricing_config.

Two pricing modes are supported:

  • Percentage markup — a basis-points rate applied on top of the underlying base price. For example, 500 basis points = 5%.
  • Absolute per-seat — a fixed dollar amount per seat per billing period.

Granularity is flat at the org level: a single configuration per (reseller, subtenant) pair, no per-vendor override in v1.

Pricing config is opaque to the subtenant by design. A customer organization can SELECT its own active pricing configuration — what it is being charged — but cannot inspect the underlying WYRE base rate or the markup-versus-base split. RLS policies on reseller_pricing_config enforce this.

reseller_pricing_config is append-only: there are no UPDATE or DELETE policies. A pricing change is a new row with a later effective_at timestamp. Reading “current” config means ORDER BY effective_at DESC LIMIT 1 per (reseller_org_id, subtenant_org_id).

The supersession history is itself the audit-of-truth — change provenance is preserved natively, no separate audit table is needed for the change-history slice.


When a customer organization is removed from a parent reseller, the structural relationship is severed cleanly:

  • The customer’s parent_org_id becomes NULL (or is reassigned, in a re-parenting case). Note that parent_org_id uses ON DELETE RESTRICT, so deleting a reseller while it still has live customers fails loudly rather than silently cascading.
  • conduit_is_reseller_admin_of_ancestor no longer returns true for the reseller’s admins against the now-detached customer.
  • Reseller-scoped pricing config is no longer effective; subsequent billing for that customer would need a new pricing model (typically standalone or under a different reseller).
  • Sub-customers of the detached customer remain attached to it (the chain-break happens at the reseller↔customer edge, not below).

Reseller-shared vendor grants do not auto-disable on severance. The grant row carries enabled = TRUE independently of the customer’s current parent_org_id. As part of the detachment workflow, the reseller-admin must explicitly disable (enabled = FALSE) or delete the relevant grant rows. Otherwise the now-detached customer can continue resolving credentials through the orphaned grant. This is by design — the operator-initiated severance step controls grant state, not the structural detachment.

There is no automatic data deletion when a customer leaves — that is a separate operator-initiated step.