Help us improve
Share bugs, ideas, or general feedback.
From superops-ai
Manages SuperOps.ai client accounts via GraphQL: create, update, search, delete; handle sites, contacts, custom fields, stages, and statuses. For MSP PSA workflows.
npx claudepluginhub wyre-technology/msp-claude-plugins --plugin superopsHow this skill is triggered — by the user, by Claude, or both
Slash command
/superops-ai:clientsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Clients (also called Accounts) are the foundation of SuperOps.ai's PSA. Every ticket, asset, and service is associated with a client. This skill covers client CRUD operations, site management, contact handling, and custom field configuration.
Manages HaloPSA clients: create, update, search customer relationships; handle fields, sites, locations, contacts, types, and onboarding workflows for MSP CRM.
Manages ConnectWise Automate client organizations: create, read, update, delete. Covers locations, settings, groups, EDPs, identifiers, and hierarchy.
Manages Syncro MSP customer records: create, update, search; handle contacts, sites/locations via REST API. For MSP client onboarding and management.
Share bugs, ideas, or general feedback.
Clients (also called Accounts) are the foundation of SuperOps.ai's PSA. Every ticket, asset, and service is associated with a client. This skill covers client CRUD operations, site management, contact handling, and custom field configuration.
| Stage | Description |
|---|---|
| Lead | Prospective client |
| Prospect | Qualified lead |
| Customer | Active paying client |
| Churned | Former client |
| Status | Description |
|---|---|
| Active | Current client |
| Inactive | Temporarily suspended |
| Archived | No longer serviced |
| Field | Type | Required | Description |
|---|---|---|---|
accountId | ID | System | Unique identifier |
name | String | Yes | Client/company name |
stage | Enum | No | Lead, Prospect, Customer, Churned |
status | Enum | No | Active, Inactive, Archived |
emailDomains | [String] | No | Associated email domains |
website | String | No | Company website |
phone | String | No | Primary phone number |
| Field | Type | Description |
|---|---|---|
industry | String | Industry type |
employeeCount | Int | Number of employees |
annualRevenue | Decimal | Annual revenue |
accountManager | Technician | Assigned account manager |
primaryContact | Contact | Main point of contact |
| Field | Type | Description |
|---|---|---|
address | String | Street address |
city | String | City |
state | String | State/province |
country | String | Country |
postalCode | String | ZIP/postal code |
mutation createClientV2($input: CreateClientInputV2!) {
createClientV2(input: $input) {
accountId
name
stage
status
emailDomains
createdTime
}
}
Variables:
{
"input": {
"name": "Acme Corporation",
"stage": "Customer",
"status": "Active",
"emailDomains": ["acme.com", "acmecorp.com"],
"website": "https://www.acme.com",
"phone": "+1-555-123-4567",
"industry": "Technology",
"employeeCount": 150,
"address": {
"street": "123 Main Street",
"city": "San Francisco",
"state": "CA",
"country": "USA",
"postalCode": "94102"
},
"accountManager": {
"email": "sarah.tech@msp.com"
}
}
}
query getClientList($input: ListInfoInput!) {
getClientList(input: $input) {
clients {
accountId
name
stage
status
emailDomains
phone
website
industry
employeeCount
accountManager {
id
name
email
}
primaryContact {
id
name
email
}
createdTime
lastUpdatedTime
}
listInfo {
totalCount
hasNextPage
endCursor
}
}
}
Variables - Active Customers:
{
"input": {
"first": 50,
"filter": {
"stage": "Customer",
"status": "Active"
},
"orderBy": {
"field": "name",
"direction": "ASC"
}
}
}
Variables - Search by Name:
{
"input": {
"first": 20,
"filter": {
"name": {
"contains": "Acme"
}
}
}
}
query getClient($input: ClientIdentifierInput!) {
getClient(input: $input) {
accountId
name
stage
status
emailDomains
website
phone
industry
employeeCount
annualRevenue
address {
street
city
state
country
postalCode
}
accountManager {
id
name
email
phone
}
primaryContact {
id
name
email
phone
}
sites {
id
name
address
isDefault
}
customFields {
name
value
}
createdTime
lastUpdatedTime
}
}
Variables:
{
"input": {
"accountId": "client-uuid-here"
}
}
mutation updateClient($input: UpdateClientInput!) {
updateClient(input: $input) {
accountId
name
stage
status
lastUpdatedTime
}
}
Variables:
{
"input": {
"accountId": "client-uuid",
"stage": "Customer",
"status": "Active",
"accountManager": {
"id": "tech-uuid"
},
"customFields": [
{
"name": "Contract Type",
"value": "Managed Services"
},
{
"name": "Monthly Retainer",
"value": "5000"
}
]
}
}
Soft Delete (Recoverable):
mutation softDeleteClients($input: DeleteClientsInput!) {
softDeleteClients(input: $input)
}
Hard Delete (Permanent):
mutation hardDeleteClients($input: DeleteClientsInput!) {
hardDeleteClients(input: $input)
}
Restore Deleted Clients:
mutation restoreClients($input: RestoreClientsInput!) {
restoreClients(input: $input)
}
Variables:
{
"input": {
"accountIds": ["client-uuid-1", "client-uuid-2"]
}
}
mutation createSite($input: CreateSiteInput!) {
createSite(input: $input) {
id
name
address {
street
city
state
country
postalCode
}
isDefault
client {
accountId
name
}
}
}
Variables:
{
"input": {
"clientId": "client-uuid",
"name": "San Francisco Office",
"address": {
"street": "456 Market Street",
"city": "San Francisco",
"state": "CA",
"country": "USA",
"postalCode": "94103"
},
"isDefault": false,
"phone": "+1-555-234-5678",
"timezone": "America/Los_Angeles"
}
}
query getClientSites($input: ClientSitesInput!) {
getClientSites(input: $input) {
sites {
id
name
address {
street
city
state
country
postalCode
}
phone
timezone
isDefault
assetCount
contactCount
}
listInfo {
totalCount
}
}
}
mutation updateSite($input: UpdateSiteInput!) {
updateSite(input: $input) {
id
name
isDefault
lastUpdatedTime
}
}
mutation createRequester($input: CreateRequesterInput!) {
createRequester(input: $input) {
id
firstName
lastName
email
phone
title
client {
accountId
name
}
site {
id
name
}
}
}
Variables:
{
"input": {
"clientId": "client-uuid",
"firstName": "John",
"lastName": "Smith",
"email": "john.smith@acme.com",
"phone": "+1-555-345-6789",
"title": "IT Manager",
"siteId": "site-uuid",
"isPrimaryContact": true
}
}
query getClientRequesters($input: ClientRequestersInput!) {
getClientRequesters(input: $input) {
requesters {
id
firstName
lastName
email
phone
title
site {
id
name
}
isPrimaryContact
isVIP
}
listInfo {
totalCount
hasNextPage
}
}
}
mutation updateRequester($input: UpdateRequesterInput!) {
updateRequester(input: $input) {
id
firstName
lastName
email
phone
lastUpdatedTime
}
}
mutation onboardClient($client: CreateClientInputV2!, $site: CreateSiteInput!, $contact: CreateRequesterInput!) {
createClientV2(input: $client) {
accountId
name
}
}
Then create site and primary contact.
query searchClients($input: ListInfoInput!) {
getClientList(input: $input) {
clients {
accountId
name
emailDomains
status
}
}
}
Variables for fuzzy search:
{
"input": {
"filter": {
"or": [
{ "name": { "contains": "acme" } },
{ "emailDomains": { "contains": "acme.com" } }
]
},
"first": 10
}
}
query getClientHealth($clientId: ID!) {
getClient(input: { accountId: $clientId }) {
name
status
}
getClientAssets: getAssetList(input: {
filter: { client: { accountId: $clientId } }
}) {
listInfo { totalCount }
}
getClientTickets: getTicketList(input: {
filter: {
client: { accountId: $clientId },
status: ["Open", "In Progress"]
}
}) {
listInfo { totalCount }
}
getClientAlerts: getAlertList(input: {
filter: {
client: { accountId: $clientId },
status: "Active"
}
}) {
listInfo { totalCount }
}
}
| Error | Cause | Resolution |
|---|---|---|
| Client not found | Invalid account ID | Verify client exists |
| Duplicate name | Client name exists | Use unique name |
| Invalid email domain | Malformed domain | Check domain format |
| Permission denied | Insufficient access | Check user permissions |
| Rate limit exceeded | Over 800 req/min | Implement backoff |
// Validate client input
function validateClientInput(input) {
const errors = [];
if (!input.name || input.name.trim().length < 2) {
errors.push('Client name must be at least 2 characters');
}
if (input.emailDomains) {
const domainRegex = /^[a-zA-Z0-9][a-zA-Z0-9-]*\.[a-zA-Z]{2,}$/;
input.emailDomains.forEach(domain => {
if (!domainRegex.test(domain)) {
errors.push(`Invalid email domain: ${domain}`);
}
});
}
if (input.website && !input.website.startsWith('http')) {
errors.push('Website must be a valid URL');
}
return errors;
}