From kaseya-it-glue
Manages IT Glue flexible assets for structured MSP documentation, covering custom types, field definitions, instances, cross-linking resources, and schema building.
npx claudepluginhub wyre-technology/msp-claude-plugins --plugin it-glueThis skill uses the workspace's default tool permissions.
Flexible Assets in IT Glue provide customizable, structured documentation templates. Unlike free-form documents, flexible assets have defined fields and types, enabling consistent documentation across organizations and powerful filtering/searching capabilities.
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.
Flexible Assets in IT Glue provide customizable, structured documentation templates. Unlike free-form documents, flexible assets have defined fields and types, enabling consistent documentation across organizations and powerful filtering/searching capabilities.
Flexible Asset Types define the schema for a category of documentation:
Flexible Asset Type: Network Overview
├── Fields:
│ ├── Primary ISP (Text)
│ ├── Backup ISP (Text)
│ ├── Public IP Addresses (Textarea)
│ ├── Firewall (Tag - Configuration)
│ ├── Network Diagram (Upload)
│ └── DNS Provider (Select)
| Type | Description | Use Case |
|---|---|---|
| Text | Single line text | Names, identifiers |
| Textarea | Multi-line text | Descriptions, notes |
| Number | Numeric value | Quantities, counts |
| Date | Date picker | Expiration dates |
| Checkbox | Boolean true/false | Flags, toggles |
| Select | Dropdown selection | Predefined options |
| Tag | Link to IT Glue resource | Configurations, contacts |
| Password | Password field | Embedded credentials |
| Upload | File attachment | Diagrams, documents |
| Percent | Percentage value | Utilization, progress |
| Header | Section header | Visual organization |
Tag fields create relationships to other IT Glue resources:
| Tag Type | Links To |
|---|---|
| Configuration | Configuration items |
| Contact | Contacts |
| Password | Passwords |
| Document | Documents |
| Flexible Asset | Other flexible assets |
| Location | Locations |
| Field | Type | Description |
|---|---|---|
id | integer | Type identifier |
name | string | Type name |
description | string | Type description |
icon | string | Display icon |
enabled | boolean | Type enabled status |
| Field | Type | Description |
|---|---|---|
id | integer | Asset instance identifier |
organization-id | integer | Parent organization |
flexible-asset-type-id | integer | Type definition |
name | string | Auto-generated from name field |
traits | object | Field values |
{
"traits": {
"primary-isp": "Comcast Business",
"backup-isp": "Verizon FiOS",
"public-ip-addresses": "203.0.113.10\n203.0.113.11",
"firewall": {
"type": "Tag",
"values": [{"id": 12345, "type": "Configuration"}]
},
"dns-provider": "Cloudflare"
}
}
Flexible asset type IDs are instance-specific — every IT Glue account has different IDs. Never guess or hardcode type IDs. Always call list_flexible_asset_types first to discover what types exist, then use the returned IDs with search_flexible_assets.
Step 1: list_flexible_asset_types → get type IDs
Step 2: search_flexible_assets(flexible_asset_type_id=<id from step 1>)
GET /flexible-asset-types
x-api-key: YOUR_API_KEY
Content-Type: application/vnd.api+json
GET /flexible-asset-types/123
x-api-key: YOUR_API_KEY
With Field Definitions:
GET /flexible-asset-types/123?include=flexible-asset-fields
GET /flexible-assets
x-api-key: YOUR_API_KEY
By Organization:
GET /organizations/456/relationships/flexible-assets
By Type:
GET /flexible-assets?filter[flexible-asset-type-id]=123
By Organization and Type:
GET /flexible-assets?filter[organization-id]=456&filter[flexible-asset-type-id]=123
GET /flexible-assets/789
x-api-key: YOUR_API_KEY
With Includes:
GET /flexible-assets/789?include=organization,flexible-asset-type
POST /flexible-assets
Content-Type: application/vnd.api+json
x-api-key: YOUR_API_KEY
{
"data": {
"type": "flexible-assets",
"attributes": {
"organization-id": 456,
"flexible-asset-type-id": 123,
"traits": {
"name": "Acme Corp Network Overview",
"primary-isp": "Comcast Business",
"backup-isp": "Verizon FiOS",
"public-ip-addresses": "203.0.113.10\n203.0.113.11",
"dns-provider": "Cloudflare"
}
}
}
}
{
"data": {
"type": "flexible-assets",
"attributes": {
"organization-id": 456,
"flexible-asset-type-id": 123,
"traits": {
"name": "Main Office Network",
"firewall": [12345],
"core-switch": [67890],
"network-admin": [11111]
}
}
}
}
PATCH /flexible-assets/789
Content-Type: application/vnd.api+json
x-api-key: YOUR_API_KEY
{
"data": {
"type": "flexible-assets",
"attributes": {
"traits": {
"backup-isp": "AT&T Business",
"last-reviewed": "2024-02-15"
}
}
}
}
DELETE /flexible-assets/789
x-api-key: YOUR_API_KEY
Fields:
├── Name (Text) - Required, Name field
├── Primary ISP (Text)
├── Backup ISP (Text)
├── Public IP Addresses (Textarea)
├── Internal IP Scheme (Textarea)
├── Firewall (Tag - Configuration)
├── Core Switch (Tag - Configuration)
├── Network Diagram (Upload)
├── DNS Provider (Select)
└── Last Reviewed (Date)
Fields:
├── Name (Text) - Required, Name field
├── Description (Textarea)
├── Version (Text)
├── Vendor (Text)
├── Support Contact (Tag - Contact)
├── Primary Server (Tag - Configuration)
├── Database Server (Tag - Configuration)
├── Admin URL (Text)
├── Admin Credentials (Password)
├── License Key (Password)
├── License Expiration (Date)
├── Documentation URL (Text)
└── Notes (Textarea)
Fields:
├── Name (Text) - Required, Name field
├── Backup Solution (Select)
├── Backup Server (Tag - Configuration)
├── Retention Policy (Textarea)
├── Backup Schedule (Textarea)
├── Data Protected (Textarea)
├── Recovery Time Objective (Text)
├── Recovery Point Objective (Text)
├── Last Test Date (Date)
├── Backup Admin Credentials (Password)
└── Notes (Textarea)
Fields:
├── Name (Text) - Required, Name field
├── Tenant ID (Text)
├── Primary Domain (Text)
├── Additional Domains (Textarea)
├── License Summary (Textarea)
├── Admin Portal URL (Text)
├── Global Admin (Tag - Contact)
├── Admin Credentials (Password)
├── MFA Status (Select)
├── Conditional Access (Checkbox)
└── Notes (Textarea)
async function createFlexibleAssetFromType(orgId, typeId, fieldValues) {
// Get the type definition to understand fields
const assetType = await getFlexibleAssetType(typeId, {
include: 'flexible-asset-fields'
});
// Validate required fields
const requiredFields = assetType.included?.filter(
f => f.attributes.required
) || [];
for (const field of requiredFields) {
const fieldKey = field.attributes['name-key'];
if (!fieldValues[fieldKey]) {
throw new Error(`Required field missing: ${field.attributes.name}`);
}
}
// Create the flexible asset
return await createFlexibleAsset({
'organization-id': orgId,
'flexible-asset-type-id': typeId,
traits: fieldValues
});
}
async function findFlexibleAssetsByType(orgId, typeName) {
// First, find the type by name
const types = await fetchFlexibleAssetTypes();
const type = types.find(t =>
t.attributes.name.toLowerCase() === typeName.toLowerCase()
);
if (!type) {
throw new Error(`Flexible asset type not found: ${typeName}`);
}
// Then fetch assets of that type for the org
return await fetchFlexibleAssets({
filter: {
'organization-id': orgId,
'flexible-asset-type-id': type.id
}
});
}
async function updateFlexibleAssetTags(assetId, fieldName, newTagIds) {
return await updateFlexibleAsset(assetId, {
traits: {
[fieldName]: newTagIds
}
});
}
async function exportFlexibleAssets(orgId, typeId) {
const assets = await fetchFlexibleAssets({
filter: {
'organization-id': orgId,
'flexible-asset-type-id': typeId
}
});
const type = await getFlexibleAssetType(typeId, {
include: 'flexible-asset-fields'
});
const fieldNames = type.included?.reduce((acc, f) => {
acc[f.attributes['name-key']] = f.attributes.name;
return acc;
}, {}) || {};
return assets.map(asset => {
const exportData = { id: asset.id };
Object.entries(asset.attributes.traits || {}).forEach(([key, value]) => {
const fieldName = fieldNames[key] || key;
exportData[fieldName] = value;
});
return exportData;
});
}
async function flexibleAssetHealthCheck(orgId) {
// Get all flexible asset types
const types = await fetchFlexibleAssetTypes();
const results = [];
for (const type of types) {
const assets = await fetchFlexibleAssets({
filter: {
'organization-id': orgId,
'flexible-asset-type-id': type.id
}
});
results.push({
type: type.attributes.name,
count: assets.length,
hasAssets: assets.length > 0
});
}
return {
totalTypes: types.length,
typesWithData: results.filter(r => r.hasAssets).length,
details: results
};
}
async function cloneFlexibleAsset(sourceAssetId, targetOrgId) {
// Get source asset
const source = await getFlexibleAsset(sourceAssetId);
// Clone traits (remove tag fields that won't be valid in new org)
const traits = { ...source.attributes.traits };
// Note: Tag fields reference resources in the source org
// You may need to map or remove these
return await createFlexibleAsset({
'organization-id': targetOrgId,
'flexible-asset-type-id': source.attributes['flexible-asset-type-id'],
traits: traits
});
}
| Code | Message | Resolution |
|---|---|---|
| 400 | Organization required | Include organization-id |
| 400 | Type required | Include flexible-asset-type-id |
| 401 | Invalid API key | Check IT_GLUE_API_KEY |
| 404 | Asset not found | Verify asset ID |
| 422 | Invalid trait value | Check field type requirements |
| 422 | Required field missing | Provide all required traits |
| Error | Cause | Fix |
|---|---|---|
| Organization required | No org ID | Include organization-id |
| Type required | No type ID | Include flexible-asset-type-id |
| Invalid trait | Wrong data type | Match field type |
| Required trait | Missing required field | Add trait value |
| Invalid tag | Bad resource ID | Verify tagged resource exists |
async function safeCreateFlexibleAsset(data) {
try {
return await createFlexibleAsset(data);
} catch (error) {
if (error.status === 422) {
const errors = error.errors || [];
// Handle missing required fields
const missingFields = errors.filter(e =>
e.detail?.includes('required') || e.detail?.includes("can't be blank")
);
if (missingFields.length > 0) {
console.log('Missing required fields:',
missingFields.map(e => e.source?.pointer)
);
// Get type definition to see required fields
const type = await getFlexibleAssetType(data['flexible-asset-type-id'], {
include: 'flexible-asset-fields'
});
const required = type.included?.filter(f => f.attributes.required);
console.log('Required fields:', required?.map(f => f.attributes.name));
}
// Handle invalid tag references
const invalidTags = errors.filter(e =>
e.detail?.includes('invalid') && e.source?.pointer?.includes('traits')
);
if (invalidTags.length > 0) {
console.log('Invalid tag references found. Removing...');
// Remove invalid tags and retry
// (implementation depends on which fields are tags)
}
}
throw error;
}
}