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)
| Control | Backed by | Notes |
|---|---|---|
| Search / sort / pagination | getCompanies() → GET /company?skip&limit&search&sort&order | Agency-scoped server-side |
| Add Company | router link → /agency/companies/create | Gated by agency.add_client_subscription in the UI |
Create — Standard (companies/Edit.vue in create mode)
| Field | Endpoint | Notes |
|---|---|---|
| Company name, website, industry, owner name/email/password, package | createCompany → POST /agencies/companies | Requires 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 / action | Permission / gate | Backend |
|---|---|---|
| Company info, owner info, subscription card | view | getCompanyById → GET /company/:id; subscription via GET /company/:id/subscriptions?limit=1 |
| Login as Owner (impersonate) | agency.impersonate_clients | impersonateUser(owner) → loginAsUser → POST /user/:id/login-as |
| Edit button | agency.add_client_subscription | → /agency/companies/:id/edit |
| Change Password (owner) | appStore.isAgencyOwner only | changeUserPassword → PUT /user/:id { new_password } |
| Widget Whitelabel Override (toggle, text, redirect URL) | view | updateWidgetWhitelabel → PUT /company/:id/widget-whitelabel { is_enable, text, url } |
| Cancel recurring add-on | agency.add_client_subscription | removeAddon(id, addonId) → POST /agencies/companies/:id/cancel-addon/:addon_id |
Package & add-on management
| Action | Repository fn | Endpoint |
|---|---|---|
| Assign a package to a company | assignPackage(companyId, packageId) | POST /agency/companies/:companyId/assign-package |
| Buy a company add-on | addAddon(companyId, addonId) | POST /agencies/buy-addon { company_id, addon_id } |
| Cancel a company add-on | removeAddon(companyId, addonId) | POST /agencies/companies/:companyId/cancel-addon/:addonId |
| Cancel company subscription | cancelCompanySubscription(companyId) | POST /agencies/companies/:id/cancel-subscription |
| Reactivate company subscription | reactivateCompanySubscription(companyId) | POST /agencies/companies/:id/reactivate-subscription |
| Archive company | archiveCompany(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 (setupOwnCompany → POST /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.
createCompanyForGHLlooks 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_dateis 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_clientsdon't see the Login-as-Owner button; the action also requires the company to have anowner. - 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_accountsfrom 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