Skip to main content
The partner console lets an agency or reseller manage a tree of descendant organizations from a single parent org. An organization holds a parent_id, so orgs form a hierarchy, and a parent can administer everything beneath it without being a member of each child. Access is gated by the child_organizations:manage scope. Holders of that scope act on any org in their subtree purely through the parent-child relationship — they do not need to be a native member of a child to list it, view it, manage its members, or read its usage. Interactive login to a child org still requires native membership, which is why creating a child adds you to it as an admin.

Authorization model

Every partner-console route runs the same checks:
  1. The caller is authenticated.
  2. The caller holds the child_organizations:manage scope — except impersonation, which requires child_organizations:impersonate instead, so it can be granted and revoked on its own.
  3. The target org is the caller’s current org or a descendant of it. Member and invitation mutations, and impersonation, additionally require the target to be a strict descendant — never your own org.
The subtree check is backed by a recursive descendant lookup (is_descendant_of) rooted at the caller’s organization. If the target is not in your subtree, the request is rejected.
Because user PATs (pat_) do not carry an organization, send the X-Anyreach-Org: <organization_id> header naming the org whose scope and subtree should apply. Org API keys (ak_) carry their org implicitly.

Capabilities

Read routes accept your own org as a target; member and invitation mutations do not. Those are strictly cross-org — managing your own org’s members goes through the in-org member routes, which require members:manage. This keeps partner-console access from implying member management over your own org.

Endpoints

All partner-console routes live under the /admin/partner-console prefix.
organizations/overview is registered before organizations/{child_id}, so overview is never matched as an org id. The overview row’s total_count is the count before paging, so it drives the pager directly.

List direct children

parent_id defaults to your current org. The search root for descendant search is always your own org, read from your token, so you cannot probe a subtree you do not own.
Each row contains id, slug, domain, domain_setup_status, and parent_id.

Search descendants

Each match returns id, slug, domain, parent_id, depth, and path — where path is a breadcrumb of slugs joined with from the root to that node, so a picker can show where a deep match sits in the tree.

Manage child members and invitations

These mirror the in-org member and invitation routes, but target a child by child_id and authorize through the subtree check instead of native membership. See /organizations/members-and-invitations for the member and invitation model. When you update a member’s roles, only roles that are valid organization roles are applied; unrecognized role IDs are dropped.

Usage view

The usage summary serves the Usage tab and supports two scopes.
The window must satisfy to after from, and the range cannot exceed 1 year (366 days). Out-of-range requests are rejected.
For the underlying metrics and how usage is metered, see /organizations/usage-and-metering.

Sub-accounts

The Sub-accounts tab lists every descendant as one row: name and breadcrumb path, admin, domain and its verification state, month-to-date usage, MRR, and when it was created. Filter by status or verified domain, search by slug, id, or domain, and export the current view — filters included — to CSV. The Admin column is the first member holding an admin role, inferred from membership. It is not a stored contact field, which is why the column is labelled Admin rather than Owner. Where a name or admin cannot be read from Logto, the cell degrades to rather than failing the page. Opening a row gives the sub-account detail view, with tabs for Overview, Members, Usage, Billing, and Activity. The detail route lives under your slug (/{your-slug}/partner-console/{child_id}), not the child’s — you administer a descendant without being a member of it, so its slug would not resolve for you.

Contracts, pricing, and revenue

Each sub-account can carry one contract: a currency, an effective date, and a per-product rate with optional included units. Revenue for a sub-account is its metered usage priced at that contract, plus any flat platform fee. The Billing tab rolls this up across the portfolio: projected revenue, your cost, gross margin, and MRR.
Sub-account contracts are for your margin and reporting. They do not feed the monthly QuickBooks run that bills you — that is driven by your own contract at the billing root. A child that has QuickBooks explicitly enabled would be billed directly as well, so the contract editor warns when it detects that case.

The margin floor

Set a min_margin_pct and the console blocks saving any sub-account rate below your cost × (1 + min_margin_pct/100). Rejections come back per product, so the editor can mark exactly which rate was too low and show the floor it had to clear. Products you are not charged for have no floor. If you have no contract of your own on file, there is no cost basis to compare against: the guard is skipped and the editor says so rather than blocking you. Because a floor is checked when a rate is saved, a later increase in your own cost can leave an existing rate below the floor — those rows are flagged in the sub-accounts billing table.
Historical months are priced at current contract rates. There is no rate history: a contract is one row per organization and editing rates replaces them. Changing a rate therefore re-prices past months in the trend chart. The chart carries this note in the UI.

Usage limits

Limits (voice minutes per month, concurrent calls, max agents) and an overage policy — block, throttle, or bill_at_rate — are recorded per sub-account. An empty limit means unlimited.
Limits are recorded but not yet enforced. They are stored so pricing and policy can be configured now; enforcement at call setup is a later phase. Do not rely on them to stop traffic today.

Activity log

Every partner-console mutation writes an append-only audit event: who acted, what changed (with a before/after diff), which org it happened in, the result, and the source IP. Member role changes, member removal, invitations, org creation, reparenting, domain retries, contract and pricing edits, and impersonation are all recorded. The log is genuinely append-only — the writing role holds INSERT and nothing else, so events cannot be edited or deleted through the API, only read back through the subtree-scoped reader.
Audit writes require the admin:audit:write scope and reads require admin:audit:read, both on the admin-service data resource. Both must be present in the service’s DATABASE_SERVICE_SCOPE environment variable. If they are missing, audit rows are silently dropped (a mutation is never failed because its audit write failed) and the Activity tab returns a permission error. The service logs AUDIT_WRITE_DENIED when this happens.

Impersonation

Impersonation starts a short-lived session as a member of a sub-account, for debugging what that user actually sees. It is gated on its own scope, child_organizations:impersonate, separate from child_organizations:manage, so administering descendants does not imply the ability to act inside them. How it is bounded:
Impersonation targets a real member rather than a synthetic support user on purpose: the point is to see exactly what that user’s roles render. A standing shadow account in every sub-account would have its own permissions and would be a permanent credential rather than an expiring one.

Create a child organization

Children are created through the standard org-create route with a parent_id, not a separate partner-console endpoint. When parent_id is set, the create flow verifies you hold child_organizations:manage and that parent_id is in your subtree before creating the org. You are added to the new org as an admin so you can sign in to it.
Omit parent_id to create a standalone top-level org instead.
1

Open the Partner Console

The page is visible to users with the child_organizations:manage scope. It opens on Sub-accounts, a filterable table of every descendant.
2

Create the sub-account

Use Create sub-account, or Create child under this org in any row’s action menu to nest it under that org. Enter a Name and Slug; the slug must be available. You are added as the new org’s admin, which is what lets you sign into it.
3

Set it up

Open the row to reach the detail view. Invite people under Members, and check the domain from the row’s DNS records action — a domain shows as Verified once its setup completes.
4

Price it

On Billing, give the sub-account a contract: currency, effective date, and a rate per product with any included units. Rates below your margin floor are rejected with the floor shown.
5

Watch it

Usage shows that sub-account’s own consumption; the portfolio-level Billing and Activity tabs roll usage, revenue, and every configuration change up across the whole subtree.

Members and invitations

How members, roles, and email invitations work within an org.

Usage and metering

What is metered and how usage summaries are computed.