git:20260713.1fbd3f3 to git:20260728.b7e78fc

57 added, 344 removed. Audit A to A.

---
name: "HaloPSA Clients"
description: >
- Use this skill when working with HaloPSA clients - creating, updating,
- searching, or managing customer relationships. Covers client fields,
- sites/locations, contacts, client types, and onboarding workflows.
- Essential for MSP account managers handling CRM in HaloPSA.
+ HaloPSA CRM data model: client records and their billing/contact fields,
+ sites (locations), contacts (Users), client classification, and parent-child
+ client hierarchy. Covers the Client/Site/Users API surface, onboarding and
+ deactivation workflows, duplicate prevention, and common validation errors.
when_to_use: >-
- When creating, updating, searching, or managing customer relationships. Use when: halopsa
- client, halo client, halopsa customer, halo customer, client management halopsa, create client
- halopsa, halopsa site, halopsa location, halopsa contact, client onboarding halo, or halopsa
- crm.
+ When creating, updating, searching, or managing customer relationships in HaloPSA.
+ Use when: halopsa client, halo client, halopsa customer, halo customer, client management
+ halopsa, create client halopsa, halopsa site, halopsa location, halopsa contact, client
+ onboarding halo, or halopsa crm.
---
# HaloPSA Client Management
## Overview
Clients (customers) are the foundation of HaloPSA. All tickets, contracts, assets, and invoices are associated with clients. Proper client data management is critical for accurate service delivery and billing.
## Key Concepts
- ### Client
-
- The primary entity representing a customer organization.
-
- | Field | Type | Required | Description |
- |-------|------|----------|-------------|
- | `id` | int | System | Unique identifier |
- | `name` | string(255) | Yes | Official company name |
- | `client_to_invoice` | int | No | Parent company for billing |
- | `toplevel_id` | int | No | Top-level parent in hierarchy |
- | `inactive` | bool | No | Active/inactive status |
- | `main_site_id` | int | No | Primary site reference |
-
- ### Client Contact Fields
-
- | Field | Type | Required | Description |
- |-------|------|----------|-------------|
- | `emailaddress` | string | No | Primary email |
- | `phonenumber` | string | No | Main phone |
- | `website` | string | No | Website URL |
- | `accountmanager_id` | int | No | Assigned account manager |
-
- ### Client Billing Fields
-
- | Field | Type | Required | Description |
- |-------|------|----------|-------------|
- | `colour` | string | No | Client color code (hex) |
- | `notes` | text | No | Internal notes |
- | `taxcode` | string | No | Tax identifier |
- | `currency_code` | string | No | Billing currency |
- | `payment_terms` | int | No | Payment terms (days) |
-
- ## Client Types
-
- HaloPSA supports client classification through custom fields and categories. Common patterns:
-
- | Type | Description | Use Case |
- |------|-------------|----------|
- | Customer | Active paying client | Full service |
- | Prospect | Potential customer | Sales pipeline |
- | Lead | Marketing qualified | Pre-sales |
- | Partner | Strategic partner | Collaboration |
- | Vendor | Supplier | Procurement |
-
- ## Sites (Locations)
-
- Sites represent physical locations for a client.
-
- ### Site Fields
-
- | Field | Type | Required | Description |
- |-------|------|----------|-------------|
- | `id` | int | System | Unique identifier |
- | `client_id` | int | Yes | Parent client |
- | `name` | string(255) | Yes | Site name |
- | `line1` | string | No | Address line 1 |
- | `line2` | string | No | Address line 2 |
- | `line3` | string | No | Address line 3 |
- | `line4` | string | No | City |
- | `postcode` | string | No | Postal/ZIP code |
- | `country` | string | No | Country |
- | `phonenumber` | string | No | Site phone |
- | `main_site` | bool | No | Is primary site |
- | `inactive` | bool | No | Active status |
-
- ## Contacts (Users)
-
- Contacts (also called Users in HaloPSA) are individuals at a client organization.
-
- ### Contact Fields
-
- | Field | Type | Required | Description |
- |-------|------|----------|-------------|
- | `id` | int | System | Unique identifier |
- | `client_id` | int | Yes | Associated client |
- | `site_id` | int | No | Associated site |
- | `name` | string(255) | Yes | Full name |
- | `firstname` | string | No | First name |
- | `surname` | string | No | Last name |
- | `emailaddress` | string | No | Email address |
- | `phonenumber` | string | No | Direct phone |
- | `mobilenumber` | string | No | Mobile phone |
- | `jobtitle` | string | No | Job title |
- | `inactive` | bool | No | Active status |
- | `isimportantcontact` | bool | No | VIP flag |
-
- ## API Patterns
-
- ### Creating a Client
-
- ```http
- POST /api/Client
- Authorization: Bearer {token}
- Content-Type: application/json
- ```
-
- ```json
- [
- {
- "name": "Acme Corporation",
- "emailaddress": "info@acme.example.com",
- "phonenumber": "555-123-4567",
- "website": "https://acme.example.com",
- "notes": "Enterprise client, premium support tier",
- "accountmanager_id": 101
- }
- ]
- ```
-
- ### Response
-
- ```json
- {
- "clients": [
- {
- "id": 123,
- "name": "Acme Corporation",
- "emailaddress": "info@acme.example.com",
- "phonenumber": "555-123-4567",
- "inactive": false
- }
- ]
- }
- ```
-
- ### Searching Clients
-
- **Search by name:**
- ```http
- GET /api/Client?search=acme
- ```
-
- **Active clients only:**
- ```http
- GET /api/Client?inactive=false
- ```
-
- **By account manager:**
- ```http
- GET /api/Client?accountmanager_id=101
- ```
-
- **Paginated with sorting:**
- ```http
- GET /api/Client?page_no=1&page_size=50&order=name&orderdesc=false
- ```
-
- ### Getting a Single Client
-
- ```http
- GET /api/Client/123
- ```
-
- **With additional details:**
- ```http
- GET /api/Client/123?includesites=true&includeusers=true
- ```
-
- ### Updating a Client
-
- ```http
- POST /api/Client
- Authorization: Bearer {token}
- Content-Type: application/json
- ```
-
- ```json
- [
- {
- "id": 123,
- "phonenumber": "555-987-6543",
- "website": "https://newsite.acme.example.com",
- "accountmanager_id": 102
- }
- ]
- ```
-
- ### Creating a Site
-
- ```http
- POST /api/Site
- Authorization: Bearer {token}
- Content-Type: application/json
- ```
-
- ```json
- [
- {
- "client_id": 123,
- "name": "Acme HQ",
- "line1": "123 Main Street",
- "line2": "Suite 500",
- "line4": "Springfield",
- "postcode": "62701",
- "country": "United States",
- "phonenumber": "555-123-4567",
- "main_site": true
- }
- ]
- ```
-
- ### Creating a Contact (User)
-
- ```http
- POST /api/Users
- Authorization: Bearer {token}
- Content-Type: application/json
- ```
-
- ```json
- [
- {
- "client_id": 123,
- "site_id": 456,
- "name": "John Smith",
- "firstname": "John",
- "surname": "Smith",
- "emailaddress": "john.smith@acme.example.com",
- "phonenumber": "555-123-4568",
- "mobilenumber": "555-987-6543",
- "jobtitle": "IT Director",
- "isimportantcontact": true
- }
- ]
- ```
+ Three linked entities make up the CRM layer:
- ### Searching Contacts
+ | Entity | Endpoint | Key relationship |
+ |--------|----------|------------------|
+ | Client | `/api/Client` | Root record; owns sites, contacts, tickets, assets, contracts |
+ | Site | `/api/Site` | `client_id` -> Client; one site may be flagged `main_site` |
+ | Contact (User) | `/api/Users` | `client_id` -> Client, optional `site_id` -> Site |
- **Contacts for a client:**
- ```http
- GET /api/Users?client_id=123
- ```
+ Most-used client fields: `id`, `name` (required), `emailaddress`, `phonenumber`,
+ `website`, `accountmanager_id`, `inactive`, `client_to_invoice`, `toplevel_id`.
+ Most-used contact fields: `name` (required), `client_id` (required), `site_id`,
+ `emailaddress`, `jobtitle`, `isimportantcontact`, `inactive`.
- **Search by email:**
- ```http
- GET /api/Users?search=john.smith@acme.example.com
- ```
+ Clients are classified (Customer, Prospect, Lead, Partner, Vendor) via custom fields
+ and categories rather than a built-in type enum.
- **Active contacts only:**
- ```http
- GET /api/Users?inactive=false&client_id=123
- ```
+ See [references/fields.md](references/fields.md) for the complete field reference for clients, sites, contacts, and client types.
## Client Hierarchy
- HaloPSA supports parent-child client relationships:
+ HaloPSA supports parent-child client relationships via two separate fields:
- ### Setting Up Hierarchy
+ - `client_to_invoice` — the client that receives the invoice
+ - `toplevel_id` — the top-level parent in the reporting hierarchy
- ```json
- [
- {
- "id": 124,
- "name": "Acme West Branch",
- "client_to_invoice": 123,
- "toplevel_id": 123
- }
- ]
- ```
+ Set both when a child should both roll up for reporting and bill to the parent.
### Hierarchy Use Cases
- **Franchise operations** - Parent company, individual locations
- **Multi-site organizations** - Headquarters with branch offices
- **Billing consolidation** - Invoice parent, service children
+ ## API Patterns
+
+ - **Create and update use the same call.** `POST /api/Client` (also `/api/Site`,
+ `/api/Users`) creates when no `id` is present and updates when `id` is supplied.
+ There is no `PUT`/`PATCH`.
+ - **The body is always a JSON array**, even for a single record. This is also how
+ you batch multiple creates/updates in one request.
+ - **Updates are partial** — send only `id` plus the fields you are changing.
+ - **Related data is opt-in** on single-record GETs: `GET /api/Client/123?includesites=true&includeusers=true`.
+ - **Pagination and sorting** use `page_no`, `page_size`, `order`, `orderdesc`.
+
+ See [references/api.md](references/api.md) for full request/response examples covering client, site, and contact create/search/update.
+
## Common Workflows
### Client Onboarding
1. **Create client record**
- Set name and contact information
- Assign account manager
- Configure billing settings
2. **Create primary site**
- Add main location address
- Set as main_site
3. **Create primary contact**
- Add key stakeholder
- Set as important contact
- Verify email address
4. **Set up contract**
- Link to client
- Define service levels
- Configure billing
5. **Deploy assets** (if applicable)
- Link devices to client
- Associate with site
- ### Contact Management
-
- ```javascript
- async function onboardContact(clientId, contactData) {
- // 1. Check for existing contact by email
- const existing = await searchUsers({
- client_id: clientId,
- search: contactData.emailaddress
- });
-
- if (existing.length > 0) {
- console.log('Contact already exists:', existing[0].id);
- return existing[0];
- }
-
- // 2. Create new contact
- const contact = await createUser({
- client_id: clientId,
- ...contactData
- });
-
- // 3. Send welcome email (if configured)
- if (contactData.send_welcome) {
- await sendWelcomeEmail(contact.id);
- }
-
- return contact;
- }
- ```
+ Search for an existing record by name or email before each create step — HaloPSA
+ does not enforce uniqueness on client names or contact emails across clients.
+ See [references/examples.md](references/examples.md) for a contact-onboarding
+ routine that does the duplicate check.
### Client Deactivation
When a client churns:
1. **Mark client inactive**
```json
[{ "id": 123, "inactive": true }]
```
2. **Close open tickets**
- Resolve or cancel pending tickets
- Document reason
3. **End contracts**
- Update contract end dates
- Process final billing
4. **Update assets**
- Return or reassign devices
- Update RMM status
- ## Error Handling
+ Never delete a client that has related records — HaloPSA returns 409 and
+ deactivation is the supported path.
- ### Common API Errors
+ ## Error Handling
| Code | Message | Resolution |
|------|---------|------------|
| 400 | Name is required | Client must have a name |
| 400 | Invalid client_id | Parent client doesn't exist |
| 400 | Duplicate email | Contact email already in use |
| 404 | Client not found | Verify client ID |
| 409 | Cannot delete - has related records | Deactivate instead |
- ### Validation Patterns
-
- ```javascript
- function validateClient(client) {
- const errors = [];
-
- if (!client.name || client.name.trim() === '') {
- errors.push('Client name is required');
- }
-
- if (client.emailaddress && !isValidEmail(client.emailaddress)) {
- errors.push('Invalid email format');
- }
-
- if (client.website && !isValidUrl(client.website)) {
- errors.push('Invalid website URL');
- }
-
- return {
- isValid: errors.length === 0,
- errors
- };
- }
- ```
-
- ## Best Practices
-
- 1. **Standardize naming** - Use consistent company name formats
- 2. **Verify before creating** - Always search first to prevent duplicates
- 3. **Maintain data quality** - Regular audits of contact information
- 4. **Use classifications** - Categorize clients for reporting
- 5. **Track account managers** - Assign for accountability
- 6. **Keep contacts current** - Deactivate departed employees
- 7. **Document relationships** - Use notes for key account information
- 8. **Set up hierarchy correctly** - Proper parent-child for billing
+ See [references/examples.md](references/examples.md) for client validation and
+ duplicate-detection helpers.
## Data Quality Queries
### Find clients without contacts
```http
GET /api/Client?hasusers=false&inactive=false
```
### Find contacts without email
```http
GET /api/Users?emailaddress=null&inactive=false
```
- ### Find duplicate client names
- ```javascript
- async function findDuplicateClients() {
- const clients = await fetchAllClients();
- const names = {};
- const duplicates = [];
+ Duplicate client names are not detectable via a query parameter — fetch and
+ normalize names client-side (see [references/examples.md](references/examples.md)).
- clients.forEach(client => {
- const normalized = client.name.toLowerCase().trim();
- if (names[normalized]) {
- duplicates.push({
- name: client.name,
- ids: [names[normalized], client.id]
- });
- } else {
- names[normalized] = client.id;
- }
- });
+ ## Best Practices
- return duplicates;
- }
- ```
+ 1. **Standardize naming** - Use consistent company name formats
+ 2. **Verify before creating** - Always search first to prevent duplicates
+ 3. **Use classifications** - Categorize clients for reporting
+ 4. **Track account managers** - Assign for accountability
+ 5. **Keep contacts current** - Deactivate departed employees
+ 6. **Document relationships** - Use notes for key account information
+ 7. **Set up hierarchy correctly** - Proper parent-child for billing
## Related Skills
- [HaloPSA Tickets](../tickets/SKILL.md) - Service tickets for clients
- [HaloPSA Contracts](../contracts/SKILL.md) - Service agreements
- [HaloPSA Assets](../assets/SKILL.md) - Client assets
- [HaloPSA API Patterns](../api-patterns/SKILL.md) - Authentication and queries