From kaseya-it-glue
Manages IT Glue documents: creates, organizes in folders, embeds passwords/resources/contacts/images, links related items, tracks versions, and uses API for listing, filtering, and pagination.
npx claudepluginhub wyre-technology/msp-claude-plugins --plugin it-glueThis skill uses the workspace's default tool permissions.
Documents in IT Glue provide structured documentation storage for organizations, enabling technicians to create runbooks, procedures, network diagrams, and general documentation. Documents support rich HTML content, embedded passwords, and relationships to other IT Glue resources.
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.
Documents in IT Glue provide structured documentation storage for organizations, enabling technicians to create runbooks, procedures, network diagrams, and general documentation. Documents support rich HTML content, embedded passwords, and relationships to other IT Glue resources.
Documents consist of:
Folders provide hierarchical organization:
Organization: Acme Corporation
└── Documents
├── Onboarding
│ ├── New User Setup
│ └── Hardware Deployment
├── Procedures
│ ├── Backup Procedures
│ └── Disaster Recovery
└── Network
├── Network Diagram
└── IP Scheme
Documents can embed:
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | System | Auto-generated unique identifier |
organization-id | integer | Yes | Parent organization |
name | string | Yes | Document title |
content | string | No | Rich HTML content |
document-folder-id | integer | No | Folder location |
| Field | Type | Description |
|---|---|---|
resource-id | integer | Related resource ID |
resource-type | string | Related resource type |
| Field | Type | Description |
|---|---|---|
created-at | datetime | Creation timestamp |
updated-at | datetime | Last update timestamp |
Always use the organization-scoped relationship endpoint — GET /documents (top-level) returns 404 in practice. Use:
GET /organizations/123/relationships/documents
x-api-key: YOUR_API_KEY
Content-Type: application/vnd.api+json
With Name Filter:
GET /organizations/123/relationships/documents?filter[name]=backup
With Pagination:
GET /organizations/123/relationships/documents?page[size]=100&page[number]=1&sort=name
Note: If documents return 404 for an organization, that organization likely does not have the IT Glue Documents module enabled. Use
search_flexible_assetsinstead — flexible assets are the more common way documentation is stored in IT Glue.
GET /documents/789
x-api-key: YOUR_API_KEY
With Includes:
GET /documents/789?include=organization,document-folder,related-items
POST /documents
Content-Type: application/vnd.api+json
x-api-key: YOUR_API_KEY
{
"data": {
"type": "documents",
"attributes": {
"organization-id": 123456,
"name": "New User Setup Procedure",
"document-folder-id": 789,
"content": "<h1>New User Setup Procedure</h1><h2>Overview</h2><p>This procedure covers the steps for setting up a new user account.</p><h2>Prerequisites</h2><ul><li>Active Directory access</li><li>Microsoft 365 admin access</li></ul><h2>Steps</h2><ol><li>Create AD account</li><li>Assign Microsoft 365 license</li><li>Configure email signature</li></ol>"
}
}
}
PATCH /documents/789
Content-Type: application/vnd.api+json
x-api-key: YOUR_API_KEY
{
"data": {
"type": "documents",
"attributes": {
"name": "Updated Document Title"
}
}
}
Important:
PATCH /documents/:idwith acontentattribute does not work for multi-section documents. Use the Document Sections API below instead.
DELETE /documents/789
x-api-key: YOUR_API_KEY
Use the sections API to read and edit the content of multi-section documents. This is the correct approach for modifying document body content — PATCH /documents/:id with a content attribute silently fails on multi-section documents.
| Type | Description |
|---|---|
Document::Heading | Heading element (renders as <h2>, etc.) |
Document::Text | Rich HTML text block |
GET /documents/789/relationships/sections
x-api-key: YOUR_API_KEY
Content-Type: application/vnd.api+json
Response:
{
"data": [
{
"id": "1001",
"type": "document-sections",
"attributes": {
"content": "<h2>Overview</h2>",
"section-type": "Document::Heading",
"position": 1
}
},
{
"id": "1002",
"type": "document-sections",
"attributes": {
"content": "<p>This procedure covers...</p>",
"section-type": "Document::Text",
"position": 2
}
}
]
}
POST /documents/789/relationships/sections
Content-Type: application/vnd.api+json
x-api-key: YOUR_API_KEY
{
"data": {
"type": "document-sections",
"attributes": {
"section-type": "Document::Text",
"content": "<p>New section content here.</p>"
}
}
}
PATCH /documents/789/relationships/sections/1002
Content-Type: application/vnd.api+json
x-api-key: YOUR_API_KEY
{
"data": {
"type": "document-sections",
"attributes": {
"content": "<p>Updated HTML content.</p>"
}
}
}
DELETE /documents/789/relationships/sections/1002
x-api-key: YOUR_API_KEY
After editing sections, publish the document to make changes visible. Use PATCH — POST returns 404.
PATCH /documents/789/publish
x-api-key: YOUR_API_KEY
No request body is required. A successful response is HTTP 200 with the updated document.
async function restructureDocument(docId, newSections) {
// 1. List existing sections
const existing = await fetch(
`${baseUrl}/documents/${docId}/relationships/sections`,
{ headers: { 'x-api-key': apiKey } }
).then(r => r.json());
// 2. Delete all existing sections
for (const section of existing.data) {
await fetch(
`${baseUrl}/documents/${docId}/relationships/sections/${section.id}`,
{ method: 'DELETE', headers: { 'x-api-key': apiKey } }
);
}
// 3. Create new sections in order
for (const section of newSections) {
await fetch(
`${baseUrl}/documents/${docId}/relationships/sections`,
{
method: 'POST',
headers: { 'x-api-key': apiKey, 'Content-Type': 'application/vnd.api+json' },
body: JSON.stringify({
data: {
type: 'document-sections',
attributes: {
'section-type': section.type, // 'Document::Heading' or 'Document::Text'
content: section.content
}
}
})
}
);
}
// 4. Publish to make changes visible (must use PATCH, not POST)
await fetch(
`${baseUrl}/documents/${docId}/publish`,
{ method: 'PATCH', headers: { 'x-api-key': apiKey } }
);
}
By Name:
GET /documents?filter[name]=backup
By Folder:
GET /documents?filter[document-folder-id]=456
GET /document-folders
x-api-key: YOUR_API_KEY
By Organization:
GET /organizations/123/relationships/document-folders
POST /document-folders
Content-Type: application/vnd.api+json
{
"data": {
"type": "document-folders",
"attributes": {
"organization-id": 123456,
"name": "Procedures",
"parent-id": null
}
}
}
{
"data": {
"type": "document-folders",
"attributes": {
"organization-id": 123456,
"name": "Disaster Recovery",
"parent-id": 789
}
}
}
Include password references in document content:
<h2>Login Credentials</h2>
<p>Domain Admin:</p>
<div data-embedded-password-id="12345"></div>
Reference configurations in documents:
<h2>Related Servers</h2>
<div data-embedded-configuration-id="67890"></div>
Include uploaded images:
<h2>Network Diagram</h2>
<img src="/uploads/organization/123/network-diagram.png" alt="Network Diagram">
POST /related-items
Content-Type: application/vnd.api+json
{
"data": {
"type": "related-items",
"attributes": {
"resource-id": 789,
"resource-type": "Document",
"destination-id": 456,
"destination-type": "Configuration",
"notes": "This document describes the configuration of this server"
}
}
}
GET /documents/789/relationships/related-items
async function createRunbook(orgId, runbookData) {
// Ensure folder exists
const folder = await ensureDocumentFolder(orgId, runbookData.folderPath);
// Build content with embedded resources
let content = `
<h1>${runbookData.title}</h1>
<h2>Overview</h2>
<p>${runbookData.overview}</p>
`;
// Add prerequisites section
if (runbookData.prerequisites?.length) {
content += `
<h2>Prerequisites</h2>
<ul>${runbookData.prerequisites.map(p => `<li>${p}</li>`).join('')}</ul>
`;
}
// Add procedure steps
if (runbookData.steps?.length) {
content += `
<h2>Procedure</h2>
<ol>${runbookData.steps.map(s => `<li>${s}</li>`).join('')}</ol>
`;
}
// Embed related passwords
if (runbookData.passwordIds?.length) {
content += `
<h2>Required Credentials</h2>
${runbookData.passwordIds.map(id =>
`<div data-embedded-password-id="${id}"></div>`
).join('')}
`;
}
// Create the document
const doc = await createDocument({
'organization-id': orgId,
name: runbookData.title,
'document-folder-id': folder?.id,
content: content
});
// Create related items
for (const configId of runbookData.relatedConfigs || []) {
await createRelatedItem({
'resource-id': doc.id,
'resource-type': 'Document',
'destination-id': configId,
'destination-type': 'Configuration'
});
}
return doc;
}
async function searchDocuments(orgId, query) {
const docs = await fetchDocuments({
filter: { 'organization-id': orgId }
});
const queryLower = query.toLowerCase();
return docs.filter(doc =>
doc.attributes.name.toLowerCase().includes(queryLower) ||
doc.attributes.content?.toLowerCase().includes(queryLower)
);
}
async function exportOrgDocumentation(orgId) {
const docs = await fetchDocuments({
filter: { 'organization-id': orgId },
include: 'document-folder'
});
return docs.map(doc => ({
name: doc.attributes.name,
folder: doc.included?.find(i =>
i.type === 'document-folders' &&
i.id === doc.relationships['document-folder']?.data?.id
)?.attributes?.name || 'Root',
content: doc.attributes.content,
createdAt: doc.attributes['created-at'],
updatedAt: doc.attributes['updated-at']
}));
}
async function documentationHealthCheck(orgId) {
const docs = await fetchDocuments({
filter: { 'organization-id': orgId }
});
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
return {
totalDocuments: docs.length,
recentlyUpdated: docs.filter(d =>
new Date(d.attributes['updated-at']) > thirtyDaysAgo
).length,
stale: docs.filter(d => {
const updated = new Date(d.attributes['updated-at']);
const yearAgo = new Date();
yearAgo.setFullYear(yearAgo.getFullYear() - 1);
return updated < yearAgo;
}).map(d => ({
name: d.attributes.name,
lastUpdated: d.attributes['updated-at']
})),
empty: docs.filter(d =>
!d.attributes.content || d.attributes.content.trim().length < 50
).map(d => d.attributes.name)
};
}
async function cloneDocumentToOrg(templateDocId, targetOrgId, newName) {
// Get template document
const template = await getDocument(templateDocId);
// Create new document with template content
return await createDocument({
'organization-id': targetOrgId,
name: newName || template.attributes.name,
content: template.attributes.content
});
}
Network Overview:
<h1>Network Overview</h1>
<h2>Network Topology</h2>
<p>[Network diagram embedded here]</p>
<h2>IP Addressing</h2>
<table>
<tr><th>Subnet</th><th>VLAN</th><th>Purpose</th></tr>
<tr><td>192.168.1.0/24</td><td>1</td><td>Servers</td></tr>
<tr><td>192.168.10.0/24</td><td>10</td><td>Workstations</td></tr>
</table>
<h2>Core Infrastructure</h2>
<p>[Embedded configuration items]</p>
<h2>Firewall Rules Summary</h2>
<p>[Rule overview]</p>
<h2>Related Credentials</h2>
<p>[Embedded passwords]</p>
Disaster Recovery:
<h1>Disaster Recovery Plan</h1>
<h2>Contact Information</h2>
<p>[Primary contacts embedded]</p>
<h2>Critical Systems</h2>
<ol>
<li>Domain Controller</li>
<li>Email Server</li>
<li>File Server</li>
</ol>
<h2>Recovery Procedures</h2>
<h3>Complete Site Failure</h3>
<ol>
<li>Activate backup site</li>
<li>Restore from cloud backup</li>
<li>Verify DNS failover</li>
</ol>
<h2>Required Credentials</h2>
<p>[Recovery passwords embedded]</p>
<h2>Vendor Contacts</h2>
<p>[Vendor contact information]</p>
| Code | Message | Resolution |
|---|---|---|
| 400 | Name can't be blank | Provide document name |
| 400 | Organization required | Include organization-id |
| 401 | Invalid API key | Check IT_GLUE_API_KEY |
| 404 | Document not found | Verify document ID |
| 404 | POST /publish returns 404 | Use PATCH not POST for publish |
| 422 | Invalid folder | Query valid folder IDs |
| Error | Cause | Fix |
|---|---|---|
| Name required | Missing name | Add name to request |
| Organization required | No org ID | Include organization-id |
| Invalid folder | Bad folder ID | Query /document-folders |
| Content too large | Exceeds size limit | Reduce content size |
async function safeCreateDocument(data) {
try {
return await createDocument(data);
} catch (error) {
if (error.status === 422) {
const errors = error.errors || [];
// Handle invalid folder
if (errors.some(e => e.detail?.includes('folder'))) {
console.log('Invalid folder. Creating at root level.');
delete data['document-folder-id'];
return await createDocument(data);
}
}
throw error;
}
}