Hudu Articles Management
Overview
Articles in Hudu serve as the knowledge base, providing a place for runbooks, procedures, network diagrams, SOPs, and general documentation. Articles support rich HTML content, can be organized into folders, and can be scoped to specific companies or kept as global (shared across all companies). MSP technicians rely on articles to quickly find procedures and reference documentation during troubleshooting.
Key Concepts
Article Scope
Articles can be scoped in two ways:
| Scope | Description | Use Case |
|---|---|---|
| Company-specific | Tied to a single company | Network diagram for Acme Corp |
| Global | Available across all companies | Standard new user setup procedure |
Article Folders
Folders organize articles within a company or globally:
Company: Acme Corporation
+-- Articles
+-- Procedures
| +-- Backup Procedure
| +-- Disaster Recovery Plan
+-- Network
| +-- Network Overview
| +-- IP Addressing Scheme
+-- Onboarding
+-- New User Setup
+-- Hardware Deployment
Article Content
Article content is stored as HTML. Hudu's editor supports:
- Headings, paragraphs, lists
- Tables
- Images (inline and uploaded)
- Code blocks
- Embedded passwords (referenced by ID)
- Links to other Hudu resources
Draft vs Published
Articles can be saved as drafts before publishing:
| State | Description |
|---|---|
| Draft | Work in progress, not visible to all users |
| Published | Visible to users with appropriate permissions |
Field Reference
Core Fields
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | System | Auto-generated unique identifier |
company_id | integer | No | Parent company (null for global articles) |
name | string | Yes | Article title |
content | string | No | Rich HTML content |
folder_id | integer | No | Folder location |
draft | boolean | No | Whether article is a draft |
slug | string | System | URL-friendly identifier |
Relationship Fields
| Field | Type | Description |
|---|---|---|
company_name | string | Parent company name (read-only) |
folder_name | string | Folder name (read-only) |
Metadata Fields
| Field | Type | Description |
|---|---|---|
created_at | datetime | Creation timestamp |
updated_at | datetime | Last update timestamp |
url | string | Full URL to article in Hudu |
object_type | string | Always "Article" |
API Patterns
List Articles
GET /api/v1/articles
x-api-key: YOUR_API_KEY
Content-Type: application/json
By Company:
GET /api/v1/articles?company_id=123
By Name:
GET /api/v1/articles?name=backup
With Pagination:
GET /api/v1/articles?company_id=123&page=1
Get Single Article
GET /api/v1/articles/456
x-api-key: YOUR_API_KEY
Response:
{
"article": {
"id": 456,
"name": "Backup Procedure - Daily Operations",
"content": "<h1>Backup Procedure</h1><h2>Overview</h2><p>The daily backup runs at 10PM...</p>",
"company_id": 123,
"company_name": "Acme Corporation",
"folder_id": 15,
"folder_name": "Procedures",
"draft": false,
"slug": "backup-procedure-daily-operations",
"created_at": "2024-06-15T10:30:00.000Z",
"updated_at": "2025-12-01T14:22:00.000Z",
"url": "https://your-company.huducloud.com/a/backup-procedure-daily-operations-abcdef"
}
}
Create Article
POST /api/v1/articles
Content-Type: application/json
x-api-key: YOUR_API_KEY
Company-specific article:
{
"article": {
"name": "New User Setup Procedure",
"company_id": 123,
"folder_id": 20,
"content": "<h1>New User Setup Procedure</h1><h2>Overview</h2><p>This procedure covers setting up a new user account for Acme Corporation.</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 with naming convention: first.last</li><li>Assign Microsoft 365 E3 license</li><li>Configure email signature using company template</li><li>Add to appropriate security groups</li></ol>"
}
}
Global article (no company_id):
{
"article": {
"name": "Standard Password Policy",
"content": "<h1>Standard Password Policy</h1><p>All managed client accounts must follow these requirements...</p><ul><li>Minimum 14 characters</li><li>Must include uppercase, lowercase, numbers, and symbols</li><li>Rotate every 90 days</li></ul>"
}
}
Update Article
PUT /api/v1/articles/456
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"article": {
"content": "<h1>Backup Procedure - Updated</h1><p>Updated backup schedule...</p>",
"draft": false
}
}
Delete Article
DELETE /api/v1/articles/456
x-api-key: YOUR_API_KEY
Archive Article
PUT /api/v1/articles/456/archive
x-api-key: YOUR_API_KEY
Folder Management
List Folders
GET /api/v1/folders
x-api-key: YOUR_API_KEY
By Company:
GET /api/v1/folders?company_id=123
Create Folder
POST /api/v1/folders
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"folder": {
"name": "Procedures",
"company_id": 123,
"parent_folder_id": null,
"description": "Standard operating procedures for Acme Corporation"
}
}
Nested Folders
{
"folder": {
"name": "Disaster Recovery",
"company_id": 123,
"parent_folder_id": 15,
"description": "DR plans and procedures"
}
}
Common Workflows
Create Comprehensive Runbook
async function createRunbook(companyId, runbookData) {
// Ensure folder exists
const folder = await ensureFolder(companyId, runbookData.folderPath);
// Build content
let content = `<h1>${runbookData.title}</h1>`;
content += `<h2>Overview</h2><p>${runbookData.overview}</p>`;
if (runbookData.prerequisites?.length) {
content += `<h2>Prerequisites</h2><ul>`;
content += runbookData.prerequisites.map(p => `<li>${p}</li>`).join('');
content += `</ul>`;
}
if (runbookData.steps?.length) {
content += `<h2>Procedure</h2><ol>`;
content += runbookData.steps.map(s => `<li>${s}</li>`).join('');
content += `</ol>`;
}
// Create the article
return await createArticle({
name: runbookData.title,
company_id: companyId,
folder_id: folder?.id,
content: content
});
}
Article Search
async function searchArticles(companyId, query) {
const articles = await fetchArticles({ company_id: companyId });
const queryLower = query.toLowerCase();
return articles.filter(article =>
article.name.toLowerCase().includes(queryLower) ||
article.content?.toLowerCase().includes(queryLower)
);
}
Documentation Health Check
async function documentationHealthCheck(companyId) {
const articles = await fetchArticles({ company_id: companyId });
const thirtyDaysAgo = new Date();
thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
const yearAgo = new Date();
yearAgo.setFullYear(yearAgo.getFullYear() - 1);
return {
totalArticles: articles.length,
drafts: articles.filter(a => a.draft).length,
recentlyUpdated: articles.filter(a =>
new Date(a.updated_at) > thirtyDaysAgo
).length,
stale: articles.filter(a =>
new Date(a.updated_at) < yearAgo
).map(a => ({
name: a.name,
lastUpdated: a.updated_at
})),
empty: articles.filter(a =>
!a.content || a.content.trim().length < 50
).map(a => a.name)
};
}
Clone Article to Another Company
async function cloneArticle(articleId, targetCompanyId, newName) {
const template = await getArticle(articleId);
return await createArtic