Hudu Assets Management
Overview
Assets in Hudu represent documented items such as servers, workstations, network devices, applications, and any other infrastructure or service an MSP needs to track. Unlike some platforms with fixed asset types, Hudu uses asset layouts -- customizable templates that define the fields and structure for each type of asset. This means your Hudu instance might have asset layouts for "Server," "Workstation," "Firewall," "Microsoft 365 Tenant," or any custom type your team defines.
Anti-triggers
- The live state of a machine — a Hudu asset is a documentation
record. It does not know whether the server is online, patched, or
alerting, and it goes stale silently. For the running endpoint use
ninjaone-devices, atera-devices, datto-rmm-devices,
ncentral-devices, or connectwise-automate-computers.
- The same record in IT Glue — IT Glue calls these Configurations and
Flexible Assets; use
itglue-configurations or
itglue-flexible-assets. The two platforms model custom fields
differently, so the field shapes do not transfer.
- A credential attached to the asset — passwords are a separate
endpoint with their own permission model; use
hudu-passwords.
- A runbook or procedure about the asset — prose documentation is
hudu-articles.
- What changed on the system recently — configuration drift is
detected elsewhere; use
liongard-detections.
- The asset row in a PSA or RMM rather than the documentation
platform — those records carry contract, ticket, and agent state
that Hudu never sees, and they are keyed independently; use
halopsa-assets, syncro-assets, or superops-assets.
Key Concepts
Asset Layouts
Asset layouts are templates that define what fields an asset of that type contains. Each layout has:
- A name (e.g., "Server," "Workstation," "Network Device")
- A set of custom fields with types (text, rich text, number, date, checkbox, dropdown, etc.)
- An icon and color for visual identification
- Optional: whether it appears in the sidebar, its position, etc.
Common asset layouts in MSP environments:
| Layout |
Description |
Typical Fields |
| Server |
Physical or virtual servers |
Hostname, IP, OS, RAM, CPU, serial |
| Workstation |
End-user devices |
Hostname, user, OS, serial, warranty |
| Network Device |
Routers, switches, firewalls |
IP, model, firmware, port count |
| Printer |
Print devices |
IP, model, serial, location |
| Application |
Software/services |
Version, license key, vendor |
| Microsoft 365 |
M365 tenant details |
Tenant ID, domain, license count |
| Backup |
Backup configuration |
Solution, server, schedule, retention |
Custom Fields
Each asset layout defines custom fields. Field types include:
| Type |
Description |
Example |
| Text |
Single-line text |
Hostname, serial number |
| RichText |
HTML rich text |
Notes, description |
| Number |
Numeric value |
RAM (GB), port count |
| Date |
Date value |
Warranty expiry, install date |
| CheckBox |
Boolean |
Monitored (yes/no) |
| Dropdown |
Predefined options |
OS type, status |
| Email |
Email address |
Admin contact |
| Phone |
Phone number |
Support line |
| Password |
Embedded password |
Admin credentials |
| AssetTag |
Link to another asset |
Host server, parent device |
| Website |
URL |
Management portal |
Asset vs Asset Layout
- Asset Layout = the template/schema (like a database table definition)
- Asset = an instance of a layout (like a row in the table)
Fields
Every asset requires company_id, asset_layout_id, and name. primary_serial, primary_model, and primary_mail are first-class columns; everything else lives in the layout-defined custom fields.
See references/fields.md for the complete field reference, including asset layout fields.
API Patterns
| Operation |
Request |
| List / filter |
GET /api/v1/assets?company_id=123&asset_layout_id=5&name=DC-01&archived=false&page=1 |
| Find by serial |
GET /api/v1/assets?primary_serial=ABC123456789 |
| Get one |
GET /api/v1/assets/789 |
| Create |
POST /api/v1/assets with { "asset": { ... } } |
| Update |
PUT /api/v1/assets/789 |
| Delete |
DELETE /api/v1/assets/789 |
| Archive / unarchive |
PUT /api/v1/assets/789/archive | /unarchive |
| Layouts |
`GET |
All requests use the x-api-key header. Request and response bodies are wrapped in a singular resource key (asset, asset_layout).
Custom field values are written as an array of single-key objects keyed by the field's snake_cased label, not as a flat object:
"custom_fields": [
{ "hostname": "dc-01.acme.local" },
{ "ip_address": "192.168.1.10" }
]
On read they come back on the asset as fields, not custom_fields.
See references/api.md for the complete endpoint catalog with request/response examples.
Common Workflows
Asset Onboarding
Layout IDs are instance-specific — resolve the layout by name before creating the asset rather than hardcoding an ID.
async function onboardAsset(companyId, assetData) {
// Step 1: Find the correct asset layout
const layouts = await fetchAssetLayouts({ name: assetData.layoutName });
const layout = layouts[0];
if (!layout) throw new Error(`Asset layout "${assetData.layoutName}" not found`);
// Step 2: Create the asset
const asset = await createAsset({
name: assetData.name,
asset_layout_id: layout.id,
company_id: companyId,
primary_serial: assetData.serialNumber,
primary_model: assetData.model,
custom_fields: assetData.customFields
});
return asset;
}
Warranty Tracking
Warranty dates live in a layout-defined custom field, so they cannot be filtered server-side — fetch and filter client-side.
async function getExpiringWarranties(daysAhead = 90) {
const today = new Date();
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + daysAhead);
// Fetch all active assets and check warranty fields
const assets = await fetchAllAssets({ archived: false });
return assets
.filter(a => {
const warrantyField = a.fields?.find(f => f.warranty_expiry);
if (!warrantyField) return false;
const warranty = new Date(warrantyField.warranty_expiry);
return warranty >= today && warranty <= futureDate;
})
.sort((a, b) => {
const aDate = new Date(a.fields.find(f => f.warranty_expiry)?.warranty_expiry);
const bDate = new Date(b.fields.find(f => f.warranty_expiry)?.warranty_expiry);
return aDate - bDate;
});
}
Asset Decommissioning
async function decommissionAsset(assetId, reason) {
// Update with decommission notes
await updateAsset(assetId, {
custom_fields: [
{ notes: `DECOMMISSIONED: ${new Date().toLocaleDateString()} - ${reason}` }
]
});
// Archive the asset
await archiveAsset(assetId);
return { status: 'archived', assetId, reason };
}
Asset Inventory by Company
async function generateAssetInventory(companyId) {
const assets = await fetchAssets({ company_id: companyId, archived: false });
const byLayout = {};
for (const asset of assets) {
const layoutName = asset.asset_layout_name || 'Unknown';
if (!byLayout[layoutName]) byLayout[layoutName] = [];
byLayout[layoutName].push({
name: asset.name,
serial: asset.primary_serial,
model: asset.primary_model,
updatedAt: asset.updated_at
});
}
return byLayout;
}
Gotchas
custom_fields on write, fields on read. Round-tripping an asset requires renaming the key.
- Custom fields are not queryable. Only
company_id, asset_layout_id, name, primary_serial, and archived filter server-side; anything layout-defined must be filtered after fetching.
- A 422 on create usually means a layout-required field is missing. Fetch the layout and inspect
fields where required: true — the error message does not name the field.
- Layout IDs differ per Hudu instance. Look them up by name; never hardcode.
- Archive is a distinct verb (
PUT /assets/:id/archive), not an archived field on update. Archived assets are excluded from default listings.
See references/errors.md for the complete error and validation table plus a recovery pattern.
Best Practices
- Standardize naming - Use consistent format (e.g., SITE-TYPE-NUM: NYC-DC-01)
- Use appropriate layouts - Choose the right asset layout for the device type
- Track serial numbers - Enable warranty lookups and asset verification
- Document custom fields - Fill in all relevant fields, not just the name
- Archive, don't delete - Preserve historical records for decommissioned assets
- Create layouts thoughtfully - Design layouts with fields MSP technicians actually need
- Keep layouts consistent - Use the same layout across all companies for the same device type
- Link related assets - Use AssetTag fields to connect VMs to hosts, apps to servers
Related Skills
1---2name: hudu-assets3description: Hudu assets and asset layouts: the layout-as-template model, custom field types, the `custom_fields` key/value array shape, archiving vs deletion, company scoping, and filter patterns across /api/v1/assets and /api/v1/asset_layouts.4---56# Hudu Assets Management78## Overview910Assets in Hudu represent documented items such as servers, workstations, network devices, applications, and any other infrastructure or service an MSP needs to track. Unlike some platforms with fixed asset types, Hudu uses **asset layouts** -- customizable templates that define the fields and structure for each type of asset. This means your Hudu instance might have asset layouts for "Server," "Workstation," "Firewall," "Microsoft 365 Tenant," or any custom type your team defines.1112## Anti-triggers1314- **The live state of a machine** — a Hudu asset is a documentation15 record. It does not know whether the server is online, patched, or16 alerting, and it goes stale silently. For the running endpoint use17 `ninjaone-devices`, `atera-devices`, `datto-rmm-devices`,18 `ncentral-devices`, or `connectwise-automate-computers`.19- **The same record in IT Glue** — IT Glue calls these Configurations and20 Flexible Assets; use `itglue-configurations` or21 `itglue-flexible-assets`. The two platforms model custom fields22 differently, so the field shapes do not transfer.23- **A credential attached to the asset** — passwords are a separate24 endpoint with their own permission model; use `hudu-passwords`.25- **A runbook or procedure about the asset** — prose documentation is26 `hudu-articles`.27- **What changed on the system recently** — configuration drift is28 detected elsewhere; use `liongard-detections`.29- **The asset row in a PSA or RMM rather than the documentation30 platform** — those records carry contract, ticket, and agent state31 that Hudu never sees, and they are keyed independently; use32 `halopsa-assets`, `syncro-assets`, or `superops-assets`.3334## Key Concepts3536### Asset Layouts3738Asset layouts are templates that define what fields an asset of that type contains. Each layout has:3940- A name (e.g., "Server," "Workstation," "Network Device")41- A set of custom fields with types (text, rich text, number, date, checkbox, dropdown, etc.)42- An icon and color for visual identification43- Optional: whether it appears in the sidebar, its position, etc.4445Common asset layouts in MSP environments:4647| Layout | Description | Typical Fields |48|--------|-------------|----------------|49| Server | Physical or virtual servers | Hostname, IP, OS, RAM, CPU, serial |50| Workstation | End-user devices | Hostname, user, OS, serial, warranty |51| Network Device | Routers, switches, firewalls | IP, model, firmware, port count |52| Printer | Print devices | IP, model, serial, location |53| Application | Software/services | Version, license key, vendor |54| Microsoft 365 | M365 tenant details | Tenant ID, domain, license count |55| Backup | Backup configuration | Solution, server, schedule, retention |5657### Custom Fields5859Each asset layout defines custom fields. Field types include:6061| Type | Description | Example |62|------|-------------|---------|63| Text | Single-line text | Hostname, serial number |64| RichText | HTML rich text | Notes, description |65| Number | Numeric value | RAM (GB), port count |66| Date | Date value | Warranty expiry, install date |67| CheckBox | Boolean | Monitored (yes/no) |68| Dropdown | Predefined options | OS type, status |69| Email | Email address | Admin contact |70| Phone | Phone number | Support line |71| Password | Embedded password | Admin credentials |72| AssetTag | Link to another asset | Host server, parent device |73| Website | URL | Management portal |7475### Asset vs Asset Layout7677- **Asset Layout** = the template/schema (like a database table definition)78- **Asset** = an instance of a layout (like a row in the table)7980### Fields8182Every asset requires `company_id`, `asset_layout_id`, and `name`. `primary_serial`, `primary_model`, and `primary_mail` are first-class columns; everything else lives in the layout-defined custom fields.8384See [references/fields.md](references/fields.md) for the complete field reference, including asset layout fields.8586## API Patterns8788| Operation | Request |89|-----------|---------|90| List / filter | `GET /api/v1/assets?company_id=123&asset_layout_id=5&name=DC-01&archived=false&page=1` |91| Find by serial | `GET /api/v1/assets?primary_serial=ABC123456789` |92| Get one | `GET /api/v1/assets/789` |93| Create | `POST /api/v1/assets` with `{ "asset": { ... } }` |94| Update | `PUT /api/v1/assets/789` |95| Delete | `DELETE /api/v1/assets/789` |96| Archive / unarchive | `PUT /api/v1/assets/789/archive` \| `/unarchive` |97| Layouts | `GET|POST /api/v1/asset_layouts` (filter with `?name=Server`) |9899All requests use the `x-api-key` header. Request and response bodies are wrapped in a singular resource key (`asset`, `asset_layout`).100101Custom field values are written as an array of single-key objects keyed by the field's snake_cased label, **not** as a flat object:102103```json104"custom_fields": [105 { "hostname": "dc-01.acme.local" },106 { "ip_address": "192.168.1.10" }107]108```109110On read they come back on the asset as `fields`, not `custom_fields`.111112See [references/api.md](references/api.md) for the complete endpoint catalog with request/response examples.113114## Common Workflows115116### Asset Onboarding117118Layout IDs are instance-specific — resolve the layout by name before creating the asset rather than hardcoding an ID.119120```javascript121async function onboardAsset(companyId, assetData) {122 // Step 1: Find the correct asset layout123 const layouts = await fetchAssetLayouts({ name: assetData.layoutName });124 const layout = layouts[0];125 if (!layout) throw new Error(`Asset layout "${assetData.layoutName}" not found`);126127 // Step 2: Create the asset128 const asset = await createAsset({129 name: assetData.name,130 asset_layout_id: layout.id,131 company_id: companyId,132 primary_serial: assetData.serialNumber,133 primary_model: assetData.model,134 custom_fields: assetData.customFields135 });136137 return asset;138}139```140141### Warranty Tracking142143Warranty dates live in a layout-defined custom field, so they cannot be filtered server-side — fetch and filter client-side.144145```javascript146async function getExpiringWarranties(daysAhead = 90) {147 const today = new Date();148 const futureDate = new Date();149 futureDate.setDate(futureDate.getDate() + daysAhead);150151 // Fetch all active assets and check warranty fields152 const assets = await fetchAllAssets({ archived: false });153154 return assets155 .filter(a => {156 const warrantyField = a.fields?.find(f => f.warranty_expiry);157 if (!warrantyField) return false;158 const warranty = new Date(warrantyField.warranty_expiry);159 return warranty >= today && warranty <= futureDate;160 })161 .sort((a, b) => {162 const aDate = new Date(a.fields.find(f => f.warranty_expiry)?.warranty_expiry);163 const bDate = new Date(b.fields.find(f => f.warranty_expiry)?.warranty_expiry);164 return aDate - bDate;165 });166}167```168169### Asset Decommissioning170171```javascript172async function decommissionAsset(assetId, reason) {173 // Update with decommission notes174 await updateAsset(assetId, {175 custom_fields: [176 { notes: `DECOMMISSIONED: ${new Date().toLocaleDateString()} - ${reason}` }177 ]178 });179180 // Archive the asset181 await archiveAsset(assetId);182183 return { status: 'archived', assetId, reason };184}185```186187### Asset Inventory by Company188189```javascript190async function generateAssetInventory(companyId) {191 const assets = await fetchAssets({ company_id: companyId, archived: false });192193 const byLayout = {};194 for (const asset of assets) {195 const layoutName = asset.asset_layout_name || 'Unknown';196 if (!byLayout[layoutName]) byLayout[layoutName] = [];197 byLayout[layoutName].push({198 name: asset.name,199 serial: asset.primary_serial,200 model: asset.primary_model,201 updatedAt: asset.updated_at202 });203 }204205 return byLayout;206}207```208209## Gotchas210211- **`custom_fields` on write, `fields` on read.** Round-tripping an asset requires renaming the key.212- **Custom fields are not queryable.** Only `company_id`, `asset_layout_id`, `name`, `primary_serial`, and `archived` filter server-side; anything layout-defined must be filtered after fetching.213- **A 422 on create usually means a layout-required field is missing.** Fetch the layout and inspect `fields` where `required: true` — the error message does not name the field.214- **Layout IDs differ per Hudu instance.** Look them up by name; never hardcode.215- **Archive is a distinct verb** (`PUT /assets/:id/archive`), not an `archived` field on update. Archived assets are excluded from default listings.216217See [references/errors.md](references/errors.md) for the complete error and validation table plus a recovery pattern.218219## Best Practices2202211. **Standardize naming** - Use consistent format (e.g., SITE-TYPE-NUM: NYC-DC-01)2222. **Use appropriate layouts** - Choose the right asset layout for the device type2233. **Track serial numbers** - Enable warranty lookups and asset verification2244. **Document custom fields** - Fill in all relevant fields, not just the name2255. **Archive, don't delete** - Preserve historical records for decommissioned assets2266. **Create layouts thoughtfully** - Design layouts with fields MSP technicians actually need2277. **Keep layouts consistent** - Use the same layout across all companies for the same device type2288. **Link related assets** - Use AssetTag fields to connect VMs to hosts, apps to servers229230## Related Skills231232- [Hudu Companies](../companies/SKILL.md) - Parent company management233- [Hudu Passwords](../passwords/SKILL.md) - Device credentials234- [Hudu Articles](../articles/SKILL.md) - Device documentation235- [Hudu Websites](../websites/SKILL.md) - Website monitoring236- [Hudu API Patterns](../api-patterns/SKILL.md) - API reference