From it-glue
Manages IT Glue contacts: creation, organization relationships, contact types, communication fields, locations, notes, PSA sync, and lookup patterns.
How this skill is triggered — by the user, by Claude, or both
Slash command
/it-glue:contactsWhen to use
When managing client contacts, contact types, locations, and communication details. Use when: it glue contact, client contact, technical contact, contact lookup, contact management, it glue contacts, organization contacts, or contact documentation.
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Contacts in IT Glue represent people associated with organizations, including clients, vendors, and partners. Proper contact management enables quick access to communication details, role information, and establishes clear points of contact for each organization.
Contacts in IT Glue represent people associated with organizations, including clients, vendors, and partners. Proper contact management enables quick access to communication details, role information, and establishes clear points of contact for each organization.
Contacts are classified by type to identify their role:
| Type | Description | Use Case |
|---|---|---|
| Primary | Main point of contact | First contact for general inquiries |
| Technical | IT-related contact | Troubleshooting, technical decisions |
| Billing | Financial contact | Invoicing, payment questions |
| Executive | C-level/management | Strategic discussions, escalations |
| End User | Regular employees | Support ticket requesters |
| Vendor | External vendor contacts | Supplier communications |
Organizations can have multiple contacts with different roles:
Organization: Acme Corporation
├── Primary Contact: John Smith (CEO)
├── Technical Contact: Jane Doe (IT Manager)
├── Billing Contact: Bob Wilson (CFO)
└── End Users:
├── Alice Brown (Sales)
├── Charlie Davis (Marketing)
└── Diana Evans (HR)
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | System | Auto-generated unique identifier |
organization-id | integer | Yes | Parent organization |
first-name | string | No | First name |
last-name | string | No | Last name |
name | string | System | Full name (auto-generated) |
title | string | No | Job title |
contact-type-id | integer | No | Type classification |
| Field | Type | Description |
|---|---|---|
contact-emails | array | Email addresses |
contact-phones | array | Phone numbers |
| Field | Type | Description |
|---|---|---|
location-id | integer | Associated location |
| Field | Type | Description |
|---|---|---|
notes | string | Contact notes (HTML) |
important | boolean | VIP/important flag |
| Field | Type | Description |
|---|---|---|
psa-id | string | PSA contact ID |
psa-integration-type | string | PSA platform type |
| Field | Type | Description |
|---|---|---|
created-at | datetime | Creation timestamp |
updated-at | datetime | Last update timestamp |
GET /contacts
x-api-key: YOUR_API_KEY
Content-Type: application/vnd.api+json
By Organization:
GET /organizations/123/relationships/contacts
With Filters:
GET /contacts?filter[organization-id]=123&filter[contact-type-id]=456
With Pagination:
GET /contacts?page[size]=100&page[number]=1&sort=name
GET /contacts/789
x-api-key: YOUR_API_KEY
With Includes:
GET /contacts/789?include=organization,location,contact-type
POST /contacts
Content-Type: application/vnd.api+json
x-api-key: YOUR_API_KEY
{
"data": {
"type": "contacts",
"attributes": {
"organization-id": 123456,
"first-name": "John",
"last-name": "Smith",
"title": "IT Manager",
"contact-type-id": 12,
"contact-emails": [
{
"value": "[email protected]",
"label-name": "Work",
"primary": true
},
{
"value": "[email protected]",
"label-name": "Personal",
"primary": false
}
],
"contact-phones": [
{
"value": "555-123-4567",
"label-name": "Office",
"primary": true
},
{
"value": "555-987-6543",
"label-name": "Mobile",
"primary": false
}
],
"notes": "<p>Primary technical contact. Available M-F 9-5.</p>",
"important": true
}
}
}
PATCH /contacts/789
Content-Type: application/vnd.api+json
x-api-key: YOUR_API_KEY
{
"data": {
"type": "contacts",
"attributes": {
"title": "Senior IT Manager",
"notes": "<p>Promoted to Senior IT Manager. Still primary contact.</p>"
}
}
}
DELETE /contacts/789
x-api-key: YOUR_API_KEY
By Name:
GET /contacts?filter[name]=John Smith
By Organization:
GET /contacts?filter[organization-id]=123
By PSA ID:
GET /contacts?filter[psa-id]=54321
{
"contact-emails": [
{
"value": "[email protected]",
"label-name": "Work",
"primary": true
}
]
}
Label Names:
To update emails, include the full array in your PATCH request.
{
"contact-phones": [
{
"value": "555-123-4567",
"label-name": "Office",
"primary": true,
"extension": "123"
}
]
}
Label Names:
async function createOrgContact(orgId, contactData) {
const emails = contactData.emails.map((email, i) => ({
value: email.address,
'label-name': email.type || 'Work',
primary: i === 0
}));
const phones = contactData.phones.map((phone, i) => ({
value: phone.number,
'label-name': phone.type || 'Office',
primary: i === 0,
extension: phone.extension
}));
return await createContact({
'organization-id': orgId,
'first-name': contactData.firstName,
'last-name': contactData.lastName,
title: contactData.title,
'contact-type-id': contactData.typeId,
'contact-emails': emails,
'contact-phones': phones,
notes: contactData.notes,
important: contactData.isVip
});
}
async function getPrimaryContact(orgId) {
const contacts = await fetchContacts({
filter: {
'organization-id': orgId,
'contact-type-id': PRIMARY_CONTACT_TYPE
}
});
// Return first primary contact or null
return contacts[0] || null;
}
async function generateContactDirectory(orgId) {
const contacts = await fetchContacts({
filter: { 'organization-id': orgId },
include: 'contact-type,location'
});
return contacts.map(contact => ({
name: contact.attributes.name,
title: contact.attributes.title,
type: contact.included?.find(i =>
i.type === 'contact-types' &&
i.id === contact.relationships['contact-type']?.data?.id
)?.attributes?.name,
emails: contact.attributes['contact-emails']?.map(e => ({
email: e.value,
type: e['label-name'],
primary: e.primary
})),
phones: contact.attributes['contact-phones']?.map(p => ({
number: p.value,
type: p['label-name'],
ext: p.extension,
primary: p.primary
})),
important: contact.attributes.important
}));
}
async function syncContactFromPsa(psaContact, orgId) {
// Check if contact already exists
const existing = await findContactByPsaId(psaContact.id);
if (existing) {
// Update existing contact
return await updateContact(existing.id, {
'first-name': psaContact.firstName,
'last-name': psaContact.lastName,
title: psaContact.title,
'contact-emails': [{
value: psaContact.email,
'label-name': 'Work',
primary: true
}],
'contact-phones': [{
value: psaContact.phone,
'label-name': 'Office',
primary: true
}]
});
} else {
// Create new contact
return await createContact({
'organization-id': orgId,
'psa-id': psaContact.id,
'first-name': psaContact.firstName,
'last-name': psaContact.lastName,
title: psaContact.title,
'contact-emails': [{
value: psaContact.email,
'label-name': 'Work',
primary: true
}],
'contact-phones': [{
value: psaContact.phone,
'label-name': 'Office',
primary: true
}]
});
}
}
async function getVipContacts() {
const contacts = await fetchAllContacts({
filter: { important: true },
include: 'organization'
});
return contacts.map(c => ({
name: c.attributes.name,
organization: c.included?.find(i =>
i.type === 'organizations' &&
i.id === c.relationships.organization?.data?.id
)?.attributes?.name,
title: c.attributes.title,
email: c.attributes['contact-emails']?.find(e => e.primary)?.value,
phone: c.attributes['contact-phones']?.find(p => p.primary)?.value
}));
}
| Code | Message | Resolution |
|---|---|---|
| 400 | Organization required | Include organization-id |
| 401 | Invalid API key | Check IT_GLUE_API_KEY |
| 404 | Contact not found | Verify contact ID |
| 422 | Invalid contact type | Query valid type IDs first |
| 422 | Invalid email format | Check email syntax |
| Error | Cause | Fix |
|---|---|---|
| Organization required | No org ID | Include organization-id |
| Invalid type | Bad type ID | Query /contact-types |
| Invalid email | Malformed email | Use valid email format |
| Name required | No first or last name | Provide at least one name |
async function safeCreateContact(data) {
try {
return await createContact(data);
} catch (error) {
if (error.status === 422) {
const errors = error.errors || [];
// Handle invalid email
if (errors.some(e => e.detail?.includes('email'))) {
console.log('Invalid email format. Removing invalid emails.');
data['contact-emails'] = data['contact-emails']?.filter(
e => isValidEmail(e.value)
);
return await createContact(data);
}
// Handle missing contact type
if (errors.some(e => e.detail?.includes('contact-type'))) {
const types = await getContactTypes();
console.log('Valid contact types:', types);
}
}
throw error;
}
}
npx claudepluginhub wyre-technology/msp-claude-plugins --plugin it-glueCreates, updates, searches, and manages ConnectWise PSA contact records. Covers contact types, communication items, portal access, and company relationships. Useful for MSP CRM workflows.
Search, create, update, and manage HubSpot CRM contacts. Covers fields, lifecycle stages, lead status, and associating contacts with companies and deals.
Manages IT Glue organizations (clients/companies) including creation, search, update, and documentation of types, statuses, parent/child relationships, PSA sync, and related resources.