IT Glue Contacts — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited IT Glue Contacts (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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.
the system of record; use autotask-crm.
itglue-organizations.
itglue-passwords.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)Core identification, communication (contact-emails, contact-phones), location, documentation, PSA, and metadata fields. See references/fields.md for the complete field reference.
Contacts support the standard list/get/create/update/delete verbs, plus organization-scoped listing (/organizations/:id/relationships/contacts) and search by name/organization-id/psa-id. contact-emails and contact-phones are arrays of {value, label-name, primary} objects — a PATCH replaces the whole array rather than merging, so always resend the full list including entries you aren't changing. See references/api.md for full request/response examples and the email/phone label-name values.
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
}));
}contact-emails and contact-phones PATCH updates replace the entire array — omitting an existing entry deletes it, so always send the full set.name is system-generated from first-name/last-name; it cannot be set directly.contact-type-id references an org-specific lookup table, not a fixed enum — an unrecognized ID returns a 422, not a descriptive "invalid type" error.See references/errors.md for the common error/validation tables and an error-recovery pattern that filters invalid emails and looks up valid contact types on 422.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.