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.
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+jsonBy Organization:
GET /organizations/123/relationships/contactsWith Filters:
GET /contacts?filter[organization-id]=123&filter[contact-type-id]=456With Pagination:
GET /contacts?page[size]=100&page[number]=1&sort=nameGET /contacts/789
x-api-key: YOUR_API_KEYWith Includes:
GET /contacts/789?include=organization,location,contact-typePOST /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_KEYBy Name:
GET /contacts?filter[name]=John SmithBy Organization:
GET /contacts?filter[organization-id]=123By 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;
}
}~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.