Hudu Companies — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited Hudu Companies (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.
Companies are the foundational entity in Hudu, representing clients, vendors, or internal entities. All documentation, assets, passwords, articles, and websites are associated with a company. In Hudu, the "Company" label is customizable per instance -- some MSPs rename it to "Organization" or "Client" -- but the API endpoint is always /api/v1/companies.
Contracts, contacts, and service history live in the PSA; use autotask-crm, connectwise-psa-companies, or halopsa-clients. Hudu's id_in_integration field holds the PSA's ID precisely because the two are different records.
itglue-api-patterns for its equivalent surface.
monitoring scope, not documentation; use ninjaone-organizations, atera-customers, or datto-rmm-sites.
pax8-companies,sherweb-customers, or qbo-customers.
Unlike IT Glue, Hudu does not enforce built-in company types. Companies are typically organized using custom fields or naming conventions. Common patterns MSPs use:
| Pattern | Description | Example |
|---|---|---|
| Active Client | Currently serviced customer | Standard operational state |
| Prospect | Potential client | Pre-sales documentation |
| Vendor | Product/service supplier | Software vendors |
| Internal | Your own MSP | Internal documentation |
| Former Client | Previously serviced | Historical records |
Companies can have parent/child relationships for multi-location or multi-division clients:
Parent Company (Acme Holdings)
+-- Child: Acme East Division
+-- Child: Acme West Division
+-- Child: Acme InternationalCompanies can be matched to PSA records using the id_in_integration and integration_slug fields, enabling cross-platform lookups between Hudu and tools like ConnectWise Manage, Autotask, or HaloPSA.
| Field | Type | Required | Description |
|---|---|---|---|
id | integer | System | Auto-generated unique identifier |
name | string | Yes | Company name |
nickname | string | No | Short name or abbreviation |
company_type | string | No | Type classification |
address_line_1 | string | No | Street address line 1 |
address_line_2 | string | No | Street address line 2 |
city | string | No | City |
state | string | No | State/province |
zip | string | No | Postal code |
country_name | string | No | Country |
phone_number | string | No | Phone number |
fax_number | string | No | Fax number |
website | string | No | Company website URL |
notes | string | No | Rich text notes |
| Field | Type | Description |
|---|---|---|
id_in_integration | integer | PSA system company ID |
integration_slug | string | PSA integration identifier |
| Field | Type | Description |
|---|---|---|
parent_company_id | integer | Parent company ID |
parent_company_name | string | Parent company name (read-only) |
| Field | Type | Description |
|---|---|---|
created_at | datetime | Creation timestamp |
updated_at | datetime | Last update timestamp |
slug | string | URL-friendly identifier |
object_type | string | Always "Company" |
GET /api/v1/companies
x-api-key: YOUR_API_KEY
Content-Type: application/jsonWith Filters:
GET /api/v1/companies?name=Acme
GET /api/v1/companies?city=Springfield
GET /api/v1/companies?state=IL
GET /api/v1/companies?id_in_integration=12345
GET /api/v1/companies?search=acmeWith Pagination:
GET /api/v1/companies?page=1
GET /api/v1/companies?page=2GET /api/v1/companies/123
x-api-key: YOUR_API_KEYPOST /api/v1/companies
Content-Type: application/json
x-api-key: YOUR_API_KEY{
"company": {
"name": "New Client Corporation",
"nickname": "NCC",
"company_type": "Customer",
"address_line_1": "123 Main Street",
"city": "Portland",
"state": "OR",
"zip": "97201",
"phone_number": "555-123-4567",
"website": "https://newclient.com",
"notes": "Onboarded February 2026. Primary contact: John Smith."
}
}PUT /api/v1/companies/123
Content-Type: application/json
x-api-key: YOUR_API_KEY{
"company": {
"nickname": "NCC-UPDATED",
"notes": "Updated: New primary contact is Jane Doe (555-987-6543)."
}
}DELETE /api/v1/companies/123
x-api-key: YOUR_API_KEYWarning: Deleting a company removes all associated resources (assets, passwords, articles, etc.). Requires DELETE permission on the API key.
PUT /api/v1/companies/123/archive
x-api-key: YOUR_API_KEYPUT /api/v1/companies/123/unarchive
x-api-key: YOUR_API_KEYGET /api/v1/companies?id_in_integration=12345async function onboardClient(clientData) {
// Step 1: Create company
const company = await createCompany({
name: clientData.companyName,
nickname: clientData.nickname,
company_type: 'Customer',
address_line_1: clientData.address,
city: clientData.city,
state: clientData.state,
zip: clientData.zip,
phone_number: clientData.phone,
website: clientData.website,
notes: `Onboarded: ${new Date().toLocaleDateString()}\nPrimary contact: ${clientData.primaryContact}`
});
// Step 2: Link to PSA
if (clientData.psaId) {
await updateCompany(company.id, {
id_in_integration: clientData.psaId
});
}
return company;
}async function offboardClient(companyId, reason) {
// Add offboarding notes
await updateCompany(companyId, {
notes: `ARCHIVED: ${new Date().toLocaleDateString()} - ${reason}`
});
// Archive the company
await archiveCompany(companyId);
}async function verifyPsaSync() {
const companies = await fetchAllCompanies();
const syncStatus = {
synced: [],
unsynced: [],
mismatched: []
};
for (const company of companies) {
if (!company.id_in_integration) {
syncStatus.unsynced.push(company);
} else {
const psaCompany = await lookupPsaCompany(company.id_in_integration);
if (psaCompany) {
syncStatus.synced.push(company);
} else {
syncStatus.mismatched.push(company);
}
}
}
return syncStatus;
}async function generateCompanyReport() {
const companies = await fetchAllCompanies();
return companies.map(company => ({
name: company.name,
nickname: company.nickname,
city: company.city,
state: company.state,
psaSynced: !!company.id_in_integration,
hasWebsite: !!company.website,
createdAt: company.created_at,
updatedAt: company.updated_at
}));
}| Code | Message | Resolution |
|---|---|---|
| 400 | Name can't be blank | Provide company name |
| 400 | Name has already been taken | Use unique name |
| 401 | Invalid API key | Check HUDU_API_KEY |
| 404 | Company not found | Verify company ID and HUDU_BASE_URL |
| 422 | Validation failed | Check required fields |
| Error | Cause | Fix |
|---|---|---|
| Name required | Missing name field | Add name to request body |
| Name not unique | Duplicate company name | Use a different name |
| Invalid parent ID | Non-existent parent company | Verify parent_company_id |
async function safeCreateCompany(data) {
try {
return await createCompany(data);
} catch (error) {
if (error.status === 422 && error.message?.includes('already been taken')) {
// Company exists - find and return it
const existing = await findCompanyByName(data.name);
return existing;
}
if (error.status === 401) {
throw new Error('API key invalid or expired. Check HUDU_API_KEY.');
}
throw error;
}
}id_in_integration for cross-platform lookups~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.