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 superopsThis skill uses the workspace's default tool permissions.
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.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Analyzes BMad project state from catalog CSV, configs, artifacts, and query to recommend next skills or answer questions. Useful for help requests, 'what next', or starting BMad.
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;
}