From kaseya-it-glue
Manages IT Glue contacts: creation, types, locations, communication details, organization relationships, notes, PSA sync, and lookup patterns for client communication.
npx claudepluginhub wyre-technology/msp-claude-plugins --plugin it-glueThis skill uses the workspace's default tool permissions.
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.
Fetches up-to-date documentation from Context7 for libraries and frameworks like React, Next.js, Prisma. Use for setup questions, API references, and code examples.
Retrieves current documentation, API references, and code examples for libraries, frameworks, SDKs, CLIs, and services via Context7 CLI. Ideal for API syntax, configs, migrations, and setup queries.
Uses ctx7 CLI to fetch current library docs, manage AI coding skills (install/search/generate), and configure Context7 MCP for AI editors.
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": "john.smith@acme.com",
"label-name": "Work",
"primary": true
},
{
"value": "john.smith@gmail.com",
"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": "user@example.com",
"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;
}
}