Skip to main content

Managing Companies

Audience: Agency-success / Support / Sales · Where in app: /agency/companies (list, create, create-ghl, detail, edit, add-addon) · Plan availability: Agency tier (verify specifics)

"Companies" are the agency's client sub-accounts. Each is a normal Knock Knock company (companies collection) tagged with agency_id, owned by a web_owner user the agency creates. From this cluster of screens the agency creates clients, assigns them a package, manages add-ons, logs in as the client (impersonation), changes their owner password, overrides the widget white-label, and archives/transfers them.

What it does

  • List all client companies with search, sort, and pagination.
  • Create a client three ways: standard (manual), via the agency's GoHighLevel sub-accounts, or as the agency's own self-account.
  • View a client's company info, owner, and subscription/add-ons on a detail page.
  • Edit company fields and assign/change the subscription package.
  • Add / cancel add-ons on a client's subscription.
  • Impersonate ("Login as Owner") to manage the client's account directly.
  • Change the owner's password, override the widget's "Powered by" white-label per company, and archive / delete a company.

How it works

The list reuses the same /company endpoint as the main app — the backend scopes results to the caller's agency. Creating a client decrements available_sub_accounts on the agency and provisions a company + web_owner user + a default chatbot flow + a client subscription pointing at the chosen package.

Two creation paths exist:

  • Standard (POST /agencies/companies, createCompany) — manual client, no GHL.
  • GHL (POST /agencies/companies/ghl, createCompanyForGHL) — same provisioning, but also mints a GHL install URL so the agency can map the new Knock Knock account to a GoHighLevel sub-account. One Knock Knock account per GHL client.

Package assignment writes a SubscriptionModel row for the client (package_id, remaining_ai_credits = pkg.ai_credits, expiry derived from the agency's own subscription). The client's company admin cannot change their own plan — they hit a "contact agency" gate; only the agency changes it.

Source: frontend/src/views/agency/companies/ (List.vue, Edit.vue, CreateGHL.vue, Detail.vue, Addon.vue) and AgencyController.

Configuration & options

Companies list (companies/List.vue)

ControlBacked byNotes
Search / sort / paginationgetCompanies()GET /company?skip&limit&search&sort&orderAgency-scoped server-side
Add Companyrouter link → /agency/companies/createGated by agency.add_client_subscription in the UI

Create — Standard (companies/Edit.vue in create mode)

FieldEndpointNotes
Company name, website, industry, owner name/email/password, packagecreateCompanyPOST /agencies/companiesRequires agency.add_client_subscription; consumes one sub-account slot

Create — GHL (companies/CreateGHL.vue)

Wrapped in AgencyPermissionGate permission="agency.add_client_subscription". If GHL is not connected it shows an "Integrate Go High Level" prompt linking to /agency/ghl-instructions.

| Field | Required | Backend validation | | -------------------------------------------- | -------- | ---------------------------------------------- | ------- | ------------------------------------------------------ | | Client Business Name (company_name) | yes | required | string | | Client Website URL (website_url) | no | string | | Industry | yes (UI) | string (from COMPANY_INDUSTRIES) | | Subscription Package (package_id) | yes | required | mongoId | exists:PackageModel.\_id (must belong to this agency) | | Contact/Owner name, email, password, confirm | yes | email must be globally unused (non-playground) |

On success returns ghl_install_url; the agency follows it to map the Knock Knock account to the GHL sub-account. Email is identity-only — no emails are sent to the client from this form.

Company detail (companies/Detail.vue)

Section / actionPermission / gateBackend
Company info, owner info, subscription cardviewgetCompanyByIdGET /company/:id; subscription via GET /company/:id/subscriptions?limit=1
Login as Owner (impersonate)agency.impersonate_clientsimpersonateUser(owner)loginAsUserPOST /user/:id/login-as
Edit buttonagency.add_client_subscription/agency/companies/:id/edit
Change Password (owner)appStore.isAgencyOwner onlychangeUserPasswordPUT /user/:id { new_password }
Widget Whitelabel Override (toggle, text, redirect URL)viewupdateWidgetWhitelabelPUT /company/:id/widget-whitelabel { is_enable, text, url }
Cancel recurring add-onagency.add_client_subscriptionremoveAddon(id, addonId)POST /agencies/companies/:id/cancel-addon/:addon_id

Package & add-on management

ActionRepository fnEndpoint
Assign a package to a companyassignPackage(companyId, packageId)POST /agency/companies/:companyId/assign-package
Buy a company add-onaddAddon(companyId, addonId)POST /agencies/buy-addon { company_id, addon_id }
Cancel a company add-onremoveAddon(companyId, addonId)POST /agencies/companies/:companyId/cancel-addon/:addonId
Cancel company subscriptioncancelCompanySubscription(companyId)POST /agencies/companies/:id/cancel-subscription
Reactivate company subscriptionreactivateCompanySubscription(companyId)POST /agencies/companies/:id/reactivate-subscription
Archive companyarchiveCompany(id)GET /company/:id/archive
Permanently delete (archived)deleteCompany(id)DELETE /company/:id

Self-account (agency's own website)

The agency can install on its own site via the dashboard (setupOwnCompanyPOST /agencies/setup-company) which flags the resulting company agency_self_account: true. setupAgencyAccount (POST /agencies/setup-agency-account) converts an existing company into the agency's self-account. Self-account members can be migrated into agency staff — see Staff Management.

Behaviors & edge cases

  • Capacity guard. GHL/standard creation rejects with "Agency has no available sub accounts" when available_sub_accounts < 1. Each create decrements the counter by 1.
  • Package must belong to the agency. createCompanyForGHL looks up the package by { _id, agency_id }; a package from another agency is rejected even if the id is valid.
  • Client expiry tracks the agency. A new client subscription's expiry_date is derived from the agency's own subscription expiry (GHL flow adds 30 minutes). When the agency renews, client allowances/credits refresh.
  • Clients can't self-serve billing. The client-side plan screen shows a "contact agency" gate; all plan/add-on changes go through these agency endpoints.
  • Impersonation is permission-gated. Staff without agency.impersonate_clients don't see the Login-as-Owner button; the action also requires the company to have an owner.
  • Widget white-label URL is validated client-side (must be http/https with a dotted hostname) before save.
  • Archive vs. delete. Archive is reversible-ish (GET .../archive); permanent delete (DELETE /company/:id) removes the company and related data and is intended for already-archived companies.

Plan & limits

  • Number of clients an agency can create is bounded by total_sub_accounts from its package (verify the slot counts per plan).
  • AI credits granted to each client come from the assigned package's ai_credits (see Packages).
  • "Transfer" of a company between agencies is not a first-class button in this cluster — handle via support/admin tooling (verify process).

Technical implementation

  • Views: frontend/src/views/agency/companies/*.vue
  • Routes: frontend/src/router/agency.js (agency-companies, -create, -create-ghl, -view, -edit, -add-addon)
  • Repository: frontend/src/repositories/agency/index.js
  • Controller: backend/app/controllers/AgencyController.js (createCompany, createCompanyForGHL, setupCompany, setupAgencyAccount, cancelCompanySubscription, cancelCompanyAddon) — see backend service
  • Models: CompanyModel, SubscriptionModel, AgencyModel, PackageModel (agency-scoped client packages)
  • GHL: GoHighLevelService.getInstallUrl — see CRM integration and GoHighLevel integration