HaloPSA Assets — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited HaloPSA Assets (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.
Assets (also called Configuration Items or CIs) in HaloPSA represent managed devices, software, and other trackable items. Effective asset management is crucial for MSPs to track what's deployed at client sites, manage hardware lifecycle, and link service tickets to affected equipment.
The primary entity representing a managed device or configuration item.
| Field | Type | Required | Description |
|---|---|---|---|
id | int | System | Unique identifier |
inventory_number | string | No | Internal asset tag |
client_id | int | Yes | Associated client |
site_id | int | No | Physical location |
user_id | int | No | Assigned user |
devicetype_id | int | No | Asset type category |
status_id | int | No | Asset status |
| Field | Type | Required | Description |
|---|---|---|---|
devicename | string(255) | Yes | Device hostname/name |
serialnumber | string | No | Manufacturer serial |
assettag | string | No | Company asset tag |
barcode | string | No | Barcode identifier |
macaddress | string | No | Network MAC address |
| Field | Type | Required | Description |
|---|---|---|---|
manufacturer | string | No | Device manufacturer |
model | string | No | Device model |
operatingsystem | string | No | OS name/version |
operatingsystemversion | string | No | OS detailed version |
ipaddress | string | No | IP address |
| Field | Type | Required | Description |
|---|---|---|---|
purchasedate | date | No | Date acquired |
purchaseprice | decimal | No | Purchase cost |
warrantyexpires | date | No | Warranty end date |
lastauditdate | datetime | No | Last RMM sync |
inactive | bool | No | Active status |
| Field | Type | Required | Description |
|---|---|---|---|
contract_id | int | No | Associated contract |
supplier_id | int | No | Vendor/supplier |
notes | text | No | Asset notes |
Common asset types in HaloPSA:
| Type ID | Name | Examples |
|---|---|---|
| 1 | Workstation | Desktop, laptop |
| 2 | Server | Physical/virtual server |
| 3 | Network Device | Router, switch, firewall |
| 4 | Printer | Network/local printer |
| 5 | Mobile Device | Phone, tablet |
| 6 | Software | License, subscription |
| 7 | Other | Miscellaneous |
Note: Asset types are configurable per instance. Query /api/AssetType for your values.
| Status ID | Name | Description |
|---|---|---|
| 1 | Active | In production use |
| 2 | Spare | Available backup |
| 3 | In Repair | Under maintenance |
| 4 | Retired | End of life |
| 5 | On Order | Pending delivery |
| 6 | Lost/Stolen | Missing |
Note: Status IDs are configurable per instance. Query /api/AssetStatus for your values.
POST /api/Asset
Authorization: Bearer {token}
Content-Type: application/json[
{
"devicename": "ACME-WS-001",
"client_id": 123,
"site_id": 456,
"user_id": 789,
"devicetype_id": 1,
"status_id": 1,
"manufacturer": "Dell",
"model": "OptiPlex 7090",
"serialnumber": "ABC123XYZ",
"assettag": "ACME-001",
"operatingsystem": "Windows 11 Pro",
"operatingsystemversion": "22H2",
"ipaddress": "192.168.1.100",
"macaddress": "00:1A:2B:3C:4D:5E",
"purchasedate": "2024-01-15",
"purchaseprice": 1299.99,
"warrantyexpires": "2027-01-15"
}
]{
"assets": [
{
"id": 5001,
"devicename": "ACME-WS-001",
"client_id": 123,
"client_name": "Acme Corporation",
"site_name": "Acme HQ",
"status_id": 1,
"status_name": "Active"
}
]
}By client:
GET /api/Asset?client_id=123By site:
GET /api/Asset?site_id=456By type:
GET /api/Asset?devicetype_id=1Active assets only:
GET /api/Asset?inactive=falseSearch by name:
GET /api/Asset?search=ACME-WSWarranty expiring soon:
GET /api/Asset?warrantyexpires_before=2024-03-31GET /api/Asset/5001With related data:
GET /api/Asset/5001?includedetails=truePOST /api/Asset
Authorization: Bearer {token}
Content-Type: application/json[
{
"id": 5001,
"status_id": 3,
"notes": "Sent to vendor for motherboard repair"
}
][
{ "id": 5001, "status_id": 1 },
{ "id": 5002, "status_id": 1 },
{ "id": 5003, "status_id": 4, "inactive": true }
]When creating or updating a ticket, reference the asset:
[
{
"summary": "Workstation not booting",
"client_id": 123,
"asset_id": 5001,
"tickettype_id": 1,
"status_id": 1
}
]Parent-child relationships (e.g., server and its VMs):
[
{
"id": 5010,
"parent_id": 5009,
"notes": "VM hosted on ACME-SRV-001"
}
]Track changes via the audit log or custom fields.
HaloPSA integrates with RMM tools to auto-sync assets.
When integrated with an RMM, these fields typically auto-populate:
devicenameoperatingsystemoperatingsystemversionipaddressmacaddresslastauditdate| Field | Description |
|---|---|
ncentral_device_id | N-central device ID |
datto_device_id | Datto RMM device ID |
connectwise_device_id | Automate device ID |
async function matchOrCreateAsset(rmmDevice) {
// 1. Try to match by RMM ID
let asset = await findAssetByRmmId(rmmDevice.id);
if (!asset) {
// 2. Try to match by serial number
asset = await findAssetBySerial(rmmDevice.serialNumber);
}
if (!asset) {
// 3. Try to match by hostname + client
asset = await findAssetByHostname(
rmmDevice.hostname,
rmmDevice.clientId
);
}
if (asset) {
// Update existing
return updateAsset(asset.id, rmmDevice);
} else {
// Create new
return createAsset(rmmDevice);
}
} [{ "devicename": "New Laptop", "status_id": 5, "client_id": 123 }]user_id and site_id GET /api/Asset?purchasedate_before=2020-01-01&inactive=false GET /api/Asset?warrantyexpires_before=2024-06-30&warrantyexpires_after=2024-01-01async function auditClientAssets(clientId) {
const assets = await getClientAssets(clientId);
const report = {
total: assets.length,
active: 0,
noWarranty: [],
missingSerial: [],
oldOS: []
};
assets.forEach(asset => {
if (!asset.inactive) report.active++;
if (!asset.warrantyexpires || new Date(asset.warrantyexpires) < new Date()) {
report.noWarranty.push(asset);
}
if (!asset.serialnumber) {
report.missingSerial.push(asset);
}
if (asset.operatingsystem?.includes('Windows 10') &&
new Date(asset.purchasedate) < new Date('2020-01-01')) {
report.oldOS.push(asset);
}
});
return report;
}| Code | Message | Resolution |
|---|---|---|
| 400 | devicename required | Asset must have a name |
| 400 | client_id required | Asset must be linked to client |
| 400 | Invalid devicetype_id | Query /api/AssetType for valid IDs |
| 404 | Asset not found | Verify asset ID exists |
| 409 | Duplicate serial number | Serial already in use |
function validateAsset(asset) {
const errors = [];
if (!asset.devicename || asset.devicename.trim() === '') {
errors.push('Device name is required');
}
if (!asset.client_id) {
errors.push('Client ID is required');
}
if (asset.macaddress && !isValidMac(asset.macaddress)) {
errors.push('Invalid MAC address format');
}
if (asset.ipaddress && !isValidIP(asset.ipaddress)) {
errors.push('Invalid IP address format');
}
return {
isValid: errors.length === 0,
errors
};
}GET /api/Asset?groupby=devicetype_id&count=trueGET /api/Asset?groupby=client_id&count=trueasync function getAssetValueByClient() {
const clients = await fetchAllClients();
const results = [];
for (const client of clients) {
const assets = await getClientAssets(client.id);
const totalValue = assets.reduce(
(sum, a) => sum + (a.purchaseprice || 0), 0
);
results.push({
client_id: client.id,
client_name: client.name,
asset_count: assets.length,
total_value: totalValue
});
}
return results.sort((a, b) => b.total_value - a.total_value);
}~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.