Agency accounts

An agency is an ordinary organization with agency mode turned on. It creates and manages client organizations — each one a real, separate org with its own projects, services, domains, members and billing — and its team is placed into every client automatically. Nothing about a client is special: every tool that takes an organizationId works on a client org exactly as it does on any other org. What the agency relationship adds is the fan-out (one team, many orgs), a place to see all the clients at once, and one bill: clients are paid by the agency at a rate that improves with the number of clients.

The nine agency tools mirror the portal’s Settings → Organizations → Agency mode panel and the Clients page. Read tools need read and admin on the agency; mutations need manage and admin on the agency, except the three that create or remove authority or spend money (agency mode itself, detaching a client, and activating a client’s billing), which need owner.

Vocabulary

  • Agency — an org with is_agency = true. Your whoami and the org switcher carry the flag.
  • Client — an org whose parent_organization_id points at an agency. A client is never itself an agency, and an agency is never a client; the tools refuse both (org_is_client, org_is_agency).
  • Managed membership — a membership row in a client org that the agency wrote for one of its own team. It behaves like any membership (roles, scopes, the org switcher all work unchanged) but is stamped as agency-managed, so the agency can later re-role or remove it without touching anyone the client added by hand.
  • Team default — for each member of the agency, the role they should hold in every client (owner / admin / developer / viewer), or null for “not fanned out”.

Enabling agency mode

Portal: Settings → Organizations → Agency mode. Owner only.

{ "name": "set_agency_mode", "arguments": { "organizationId": "…", "isAgency": true } }

Two refusals: an org that is already somebody’s client cannot become an agency (detach it first), and an agency with clients cannot turn agency mode off (detach them first, has_clients).

Team defaults

Defaults decide who lands in each new client and with what role. Every current member of the agency is listed, with their agency role and their default (null until set):

{ "name": "list_agency_team_defaults", "arguments": { "organizationId": "…" } }
{ "name": "set_agency_team_default",
  "arguments": { "organizationId": "…", "principalId": "…", "defaultRole": "developer" } }

A default is a standing grant into every future client, so it is bounded by your own agency role exactly as a direct grant would be: only an agency owner can set a default of owner (role_escalation otherwise). Set defaultRole to null to stop fanning that person out. Agents are members too — an agency’s automation agent with a developer default deploys into every client with no per-client setup.

Changing a default does not by itself touch existing clients; see Applying defaults below.

Creating a client in 60 seconds

Portal: Organizations → Create organization → choose Client of and pick the agency. MCP:

{ "name": "create_client_organization",
  "arguments": { "organizationId": "<agency id>", "name": "Northwind Bakery",
                 "clientOwnerEmail": "pat@northwind.example",
                 "clientOwnerDisplayName": "Pat" } }

One transaction creates the org, activates Launch on it, links it to the agency, and writes the agency’s team in as managed memberships per the team defaults (with matching Launch access). The response lists every membership written and creatorRole — you always land in the client: with your own default if you have one, and as owner if the defaults would otherwise leave the org ownerless (an org is never born without an owner).

clientOwnerEmail is optional. When given, the client’s own owner is invited after the org exists (so an invite failure never undoes the org), and clientOwner in the response tells you what happened:

  • membership — a brand-new email: the account was created as owner and a single-use setup link is returned for you to relay (also emailed).
  • invitation — an existing account: a pending in-portal invitation the person must accept before they hold ownership.
  • error — the org was created but the invite was not; invite again with invite_member.

The new client is billed to the agency in the same call: right after the org exists it is put on a plan (planCode, default pro; interval month or year) charged to the agency’s card at the agency’s tier discount, and billing in the response says what happened — see Billing below. Client creation is rate-limited per agency.

Listing clients

{ "name": "list_agency_clients", "arguments": { "organizationId": "…" } }

One row per client: id, slug, name, tier, member and managed-member counts, project/service counts, last deploy time and state, plan code and subscription status, plus a billing block (mode, who pays, plan, cadence, discount, period end) — the same table the portal’s Clients page shows, so an agent can spot “which client has a failed deploy” or “which client is still on trial” without walking each org. The response also carries the agency’s tier and agencyHasPaymentMethod (see Billing).

Linking an existing org

Already manage a client’s org that predates agency mode? Adopt it rather than recreate it:

{ "name": "link_client_organization",
  "arguments": { "organizationId": "<agency id>", "clientOrganizationId": "<existing org id>" } }

You need admin on the agency and owner of the org being linked — linking hands the agency standing access to that org, so only its owner can consent. Preconditions: the target is not already a client (already_has_parent), is not an agency and has no clients of its own (org_is_agency), and is not the agency itself (self_link). On success the team defaults are applied immediately and the response carries the applied summary. Memberships the org already had stay unmanaged — the client granted those itself and they are never rewritten.

Applying defaults to existing clients

{ "name": "apply_agency_team_defaults",
  "arguments": { "organizationId": "<agency id>", "clientOrganizationId": "<one client, optional>" } }

Omit clientOrganizationId to sweep every client. Per client you get added, updated, removed and skipped:

  • added / updated — managed rows created or re-roled to match the current defaults.
  • removed — managed rows whose principal no longer has a default (set to null) or is no longer an agency member.
  • skipped hand_granted — the client already has this person as a hand-granted member. A client’s own decision about a person outranks the agency’s default, so the row is left alone.
  • skipped last_owner — removing this managed row would leave the client without an owner; it stays until the client has an owner of its own.

Apply is idempotent — running it twice changes nothing the second time.

Detaching a client

{ "name": "detach_client_organization",
  "arguments": { "organizationId": "<agency id>", "clientOrganizationId": "<client id>" } }

Owner of the agency. Detach removes exactly the managed memberships (and the Launch access they carried) and clears the parent link — nothing else. The client keeps every project, domain, database and hand-granted member. Two preconditions:

  • the client must have at least one owner of its own (not agency-managed) — otherwise the detach would orphan it (no_independent_owner). Invite one with invite_member, or wait for a pending owner invitation to be accepted;
  • the client must be paying for itself — detach is refused while a live subscription still sits on the agency’s card (billing_not_client_paid). A client with no live subscription (still on trial) can be detached freely.

When someone leaves the agency

Removing a member from the agency (remove_member, or the portal’s Members panel) also revokes every managed membership that person held in the agency’s clients, in the same operation. Hand-granted memberships in a client are untouched — if a client made your former colleague an owner directly, that stays. The only managed row that survives is a last_owner case, which is reported so you can fix the client’s ownership.

Demoting someone inside the agency does not change their client defaults; edit the default if the new role should not fan out.

Billing

A client is agency-paid by default: its plan subscription is created on the agency’s payment method, not the client’s, so a client never needs a card of its own while the agency pays. The agency’s card is charged per client subscription (each on the agency’s invoices), at the agency’s rate.

Agency rate

The rate is a tier discount computed from the agency’s number of active clients — clients on a live subscription (active, trialing or past due). It is never assigned by hand:

Active clients Discount
1 – 4 10 %
5 – 14 20 %
15 + 30 %

The client being activated counts toward its own band, so the very first client lands at 10 % rather than list. The discount applies to plan subscriptions only — domain registrations and renewals, SMS and phone numbers, and metered overage are pass-through and stay at list price. When the agency moves into a new band, every client subscription is re-priced at its next renewal (coupons only touch future invoices, so a band change never moves money mid-cycle). list_agency_clients reports the current tier: activeClients, discountBps and nextBand — how many clients away the next rate is. The portal’s Clients page shows the same as a tile: Agency rate: 20% off · 6 active clients · 9 more to reach 30%.

Activating a client

Creating a client activates it: create_client_organization (portal: New client, with a plan and monthly/yearly choice) creates the org and then starts its subscription on the agency’s card. The response’s billing says which of three things happened:

  • activated (or already_active) — the client is on planCode at discountBps off, billed to the agency;
  • not_activated with a code — the org exists but no subscription was started. The one to expect is payer_card_required: the agency has no payment method yet. The client simply stays on its 14-day trial. Add a card to the agency org (portal Billing → Add payment method, or set_payment_method with the agency’s organizationId), then activate the client;
  • skipped — no payment provider is configured on this host.

A client that was created before the agency had a card, or an existing org adopted with link_client_organization while still on its trial, is activated explicitly:

{ "name": "activate_client_billing",
  "arguments": { "organizationId": "<agency id>", "clientOrganizationId": "<client id>",
                 "planCode": "pro", "interval": "month" } }

Portal: Clients → Activate on the client’s row (owner only; disabled until the agency has a card). It is idempotent — a client already on a live subscription comes back as already_active and nothing is created twice. It refuses with not_a_client for an org that is not this agency’s client, and with already_client_paid for a client that pays for itself: the agency can never take over a client’s own billing. activate_client_billing requires owner on the agency, because it spends the agency’s money.

What the client sees

A client’s own Billing page — and get_billing_status on the client org — says Billed by agency name: the plan and payment method are managed by the agency, so the subscribe, change-plan and card controls are hidden there. Usage and invoices stay visible. get_billing_status returns agencyBilling with the agency’s id and name, the mode, payerIsAgency and the discountBps in force; when payerIsAgency is true, hasBillingCustomer / hasPaymentMethod describe the agency’s card, not the client’s.

Handing billing to the client

A client can later be moved to paying for itself. handoff_client_billing (portal: Clients → Hand off on the client’s row, owner only) moves the client’s plan subscription from the agency’s card onto the client’s own payment method.

Preconditions. The client is billed to the agency on a live subscription (list_agency_clients: billing.payerIsAgency true and subscriptionStatus active/trialing/past due — a trial client has nothing to hand off), and the client has a card of its own (billing.hasOwnCustomer). Without one the call refuses with client_card_required: the client org must add a payment method — set_payment_method with the client’s organizationId, or its own Billing page — and then the handoff is retried. It is always the client’s card that is missing here, never the agency’s.

The two modes. The handoff names the pricing mode the client lands in:

  • referral_discount — the client pays list minus the agency’s tier discount on its own card (the rate in force at handoff), and the agency earns nothing further on it. The client’s Billing page shows Partner discount −20% via agency name.
  • commission — the client pays list price on its own card, and the agency earns the tier difference as a commission on every paid plan invoice, recorded in the ledger below. The client’s Billing page shows Referred by agency name · list price; the commission is the platform’s to pay, never the client’s.
{ "name": "handoff_client_billing",
  "arguments": { "organizationId": "<agency id>", "clientOrganizationId": "<client id>",
                 "mode": "commission" } }

The response carries mode, newSubscriptionId, oldSubscriptionId, startsAt and discountBps.

Zero double billing. Nothing is charged twice and nothing is prorated: the agency’s current paid period runs to its end, the client’s new subscription first charges exactly then (startsAt; null means it takes over immediately), and the agency’s subscription for the client is cancelled at that same instant. The portal toast says the date; the client’s human owners receive an email — Billing for org name is now on your card — naming the agency, the rate and the start.

One-way. The agency can never take a client’s billing back (activate_client_billing refuses with already_client_paid). What can change is the mode: set_client_pricing_mode (portal: the referral / commission select on a self-paying client’s row, owner only) flips a client-paid client between referral_discount and commission from its next invoice — nothing is re-billed. It refuses with not_client_paid for an agency-paid client (hand it off instead, which takes the mode) and same_mode when nothing would change.

{ "name": "set_client_pricing_mode",
  "arguments": { "organizationId": "<agency id>", "clientOrganizationId": "<client id>",
                 "mode": "referral_discount" } }

Once a client pays for itself it can be detached (detach_client_organization); while the agency still pays, detach is refused with billing_not_client_paid.

Commission ledger and payouts

Commission accrues only for clients in commission mode paying for themselves: when one of their plan invoices is paid, one accrual row lands in the agency’s ledger with the invoice amount, the rate (commissionBps, snapshotted for that client at handoff — a later tier change does not rewrite past or future rows for an existing client) and the resulting commissionCents. Referral-discount clients earn nothing here; domains, SMS and overage never accrue.

{ "name": "list_agency_commissions",
  "arguments": { "organizationId": "<agency id>", "unpaidOnly": true, "limit": 100 } }

The response is balance (unpaidCents, paidCents, currency), commissions (newest first; kind accrual or adjustment, payoutId set once paid) and payouts. The portal’s Clients page shows the same as the Commissions card: unpaid / paid-out totals, the ledger (date, client, kind, base, rate, commission, paid/unpaid) and the payouts list. list_agency_commissions requires admin on the agency.

Payouts are manual. There is no payout rail in this release: the platform team sends the money out of band (bank transfer, Stripe transfer, credit) and records it, which settles every unpaid row in one payout for exactly their sum. A negative balance (from an adjustment) carries forward and nets against future accruals. Adjustments are signed corrections the platform team enters with a note the agency can read — a clawback for a refunded client invoice, or a goodwill credit.

Platform admins have three tools for this: admin_list_agency_commissions (every agency’s balance, or one agency’s full ledger), admin_record_agency_payout (method, optional reference / note; refuses nothing_unpaid) and admin_adjust_agency_commission (signed non-zero commissionCents, required note, optional clientOrganizationId). Their portal twin is the admin console’s organization view.

Related