---
name: "Hudu Websites"
description: >
  Use this skill when working with Hudu website records - website monitoring,
  SSL/TLS tracking, email security (DMARC, DKIM, SPF), DNS records, and
  linking websites to companies. Covers website CRUD, monitoring fields,
  and email security verification patterns.
when_to_use: "When working with website monitoring, SSL/TLS tracking, email security (DMARC, DKIM, SPF), DNS records, and linking websites to companies in Hudu website records"
triggers:
  - hudu website
  - website monitoring
  - ssl certificate
  - email security
  - dmarc check
  - dkim check
  - spf record
  - website management
  - hudu dns
  - website tracking
---

# Hudu Websites Management

## Overview

Websites in Hudu represent website records associated with client companies. Beyond basic URL tracking, Hudu provides monitoring for SSL/TLS certificates and email security standards (DMARC, DKIM, SPF). MSPs use website records to track client web properties, monitor certificate expiration, and verify email authentication configuration.

## Key Concepts

### Website Monitoring

Hudu can automatically monitor websites for:

| Monitoring Area | Description |
|----------------|-------------|
| SSL/TLS | Certificate validity, expiration date, issuer |
| DMARC | Domain-based Message Authentication, Reporting & Conformance |
| DKIM | DomainKeys Identified Mail |
| SPF | Sender Policy Framework |
| HTTP Status | Whether the site is reachable |

### Email Security Trifecta

The three email security standards that Hudu tracks:

| Standard | Purpose | DNS Record Type |
|----------|---------|----------------|
| **SPF** | Specifies authorized mail servers | TXT record on domain |
| **DKIM** | Cryptographic email authentication | TXT record on selector._domainkey |
| **DMARC** | Policy for handling failed SPF/DKIM | TXT record on _dmarc subdomain |

### Website Scope

Each website record is linked to a company. A company can have multiple website records (e.g., primary domain, marketing site, client portal).

## Field Reference

### Core Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `id` | integer | System | Auto-generated unique identifier |
| `company_id` | integer | Yes | Parent company |
| `name` | string | Yes | Website display name / URL |
| `slug` | string | System | URL-friendly identifier |
| `notes` | string | No | Additional notes |
| `paused` | boolean | No | Whether monitoring is paused |
| `disable_dns` | boolean | No | Disable DNS checks |
| `disable_ssl` | boolean | No | Disable SSL checks |
| `disable_whois` | boolean | No | Disable WHOIS checks |

### Monitoring Fields

| Field | Type | Description |
|-------|------|-------------|
| `url` | string | The monitored URL |
| `monitoring_status` | string | Current monitoring status |

### SSL/TLS Fields

| Field | Type | Description |
|-------|------|-------------|
| `ssl_status` | string | SSL certificate status |
| `ssl_expiration` | datetime | SSL certificate expiration date |
| `ssl_issuer` | string | SSL certificate issuer |

### Email Security Fields

| Field | Type | Description |
|-------|------|-------------|
| `dmarc_status` | string | DMARC record status |
| `dmarc_policy` | string | DMARC policy (none, quarantine, reject) |
| `dkim_status` | string | DKIM record status |
| `spf_status` | string | SPF record status |
| `spf_record` | string | SPF record value |

### DNS Fields

| Field | Type | Description |
|-------|------|-------------|
| `dns_a_records` | array | A record values |
| `dns_mx_records` | array | MX record values |
| `dns_ns_records` | array | NS record values |

### Metadata Fields

| Field | Type | Description |
|-------|------|-------------|
| `created_at` | datetime | Creation timestamp |
| `updated_at` | datetime | Last update timestamp |
| `object_type` | string | Always "Website" |
| `company_name` | string | Parent company name (read-only) |

## API Patterns

### List Websites

```http
GET /api/v1/websites
x-api-key: YOUR_API_KEY
Content-Type: application/json
```

**By Company:**
```http
GET /api/v1/websites?company_id=123
```

**By Name:**
```http
GET /api/v1/websites?name=acme.com
```

**With Pagination:**
```http
GET /api/v1/websites?page=1
```

### Get Single Website

```http
GET /api/v1/websites/456
x-api-key: YOUR_API_KEY
```

**Response:**
```json
{
  "website": {
    "id": 456,
    "company_id": 123,
    "company_name": "Acme Corporation",
    "name": "acme.com",
    "url": "https://www.acme.com",
    "notes": "Primary company website",
    "paused": false,
    "disable_dns": false,
    "disable_ssl": false,
    "disable_whois": false,
    "monitoring_status": "up",
    "ssl_status": "valid",
    "ssl_expiration": "2026-08-15T00:00:00.000Z",
    "ssl_issuer": "Let's Encrypt Authority X3",
    "dmarc_status": "pass",
    "dmarc_policy": "reject",
    "dkim_status": "pass",
    "spf_status": "pass",
    "spf_record": "v=spf1 include:_spf.google.com include:spf.protection.outlook.com ~all",
    "dns_a_records": ["203.0.113.10"],
    "dns_mx_records": ["aspmx.l.google.com"],
    "dns_ns_records": ["ns1.example.com", "ns2.example.com"],
    "created_at": "2024-03-15T10:30:00.000Z",
    "updated_at": "2026-02-20T08:00:00.000Z"
  }
}
```

### Create Website

```http
POST /api/v1/websites
Content-Type: application/json
x-api-key: YOUR_API_KEY
```

```json
{
  "website": {
    "name": "acme.com",
    "company_id": 123,
    "notes": "Primary company website. Hosted on AWS.",
    "paused": false,
    "disable_dns": false,
    "disable_ssl": false,
    "disable_whois": false
  }
}
```

### Update Website

```http
PUT /api/v1/websites/456
Content-Type: application/json
x-api-key: YOUR_API_KEY
```

```json
{
  "website": {
    "notes": "Primary company website. Migrated to Azure on 2026-02-15.",
    "paused": false
  }
}
```

### Delete Website

```http
DELETE /api/v1/websites/456
x-api-key: YOUR_API_KEY
```

## Common Workflows

### SSL Expiration Monitoring

```javascript
async function getExpiringSslCerts(daysAhead = 30) {
  const today = new Date();
  const futureDate = new Date();
  futureDate.setDate(futureDate.getDate() + daysAhead);

  const websites = await fetchAllWebsites();

  return websites
    .filter(w => {
      if (!w.ssl_expiration || w.paused) return false;
      const expiry = new Date(w.ssl_expiration);
      return expiry >= today && expiry <= futureDate;
    })
    .map(w => ({
      name: w.name,
      company: w.company_name,
      sslExpiration: w.ssl_expiration,
      daysRemaining: Math.ceil(
        (new Date(w.ssl_expiration) - today) / (1000 * 60 * 60 * 24)
      ),
      issuer: w.ssl_issuer
    }))
    .sort((a, b) => a.daysRemaining - b.daysRemaining);
}
```

### Email Security Audit

```javascript
async function emailSecurityAudit(companyId) {
  const websites = await fetchWebsites({ company_id: companyId });

  return websites.map(w => ({
    domain: w.name,
    spf: {
      status: w.spf_status || 'Not checked',
      record: w.spf_record || 'Not found'
    },
    dkim: {
      status: w.dkim_status || 'Not checked'
    },
    dmarc: {
      status: w.dmarc_status || 'Not checked',
      policy: w.dmarc_policy || 'Not set'
    },
    overallScore: calculateEmailSecurityScore(w)
  }));
}

function calculateEmailSecurityScore(website) {
  let score = 0;
  if (website.spf_status === 'pass') score += 33;
  if (website.dkim_status === 'pass') score += 33;
  if (website.dmarc_status === 'pass') score += 34;
  if (website.dmarc_policy === 'reject') score += 10; // bonus for strict policy
  return Math.min(score, 100);
}
```

### Website Inventory Report

```javascript
async function generateWebsiteReport(companyId) {
  const websites = await fetchWebsites({ company_id: companyId });

  return {
    total: websites.length,
    monitored: websites.filter(w => !w.paused).length,
    paused: websites.filter(w => w.paused).length,
    sslValid: websites.filter(w => w.ssl_status === 'valid').length,
    sslExpiring: websites.filter(w => {
      if (!w.ssl_expiration) return false;
      const daysLeft = Math.ceil(
        (new Date(w.ssl_expiration) - new Date()) / (1000 * 60 * 60 * 24)
      );
      return daysLeft <= 30;
    }).length,
    emailSecurityComplete: websites.filter(w =>
      w.spf_status === 'pass' &&
      w.dkim_status === 'pass' &&
      w.dmarc_status === 'pass'
    ).length,
    websites: websites.map(w => ({
      name: w.name,
      ssl: w.ssl_status,
      sslExpiry: w.ssl_expiration,
      spf: w.spf_status,
      dkim: w.dkim_status,
      dmarc: w.dmarc_status
    }))
  };
}
```

### Onboard Client Domains

```javascript
async function onboardClientDomains(companyId, domains) {
  const results = [];

  for (const domain of domains) {
    const website = await createWebsite({
      name: domain.name,
      company_id: companyId,
      notes: domain.notes || `Added during onboarding on ${new Date().toLocaleDateString()}`,
      paused: false
    });
    results.push(website);
  }

  return results;
}
```

## Error Handling

### Common API Errors

| Code | Message | Resolution |
|------|---------|------------|
| 400 | Name can't be blank | Provide website name/domain |
| 400 | Company is required | Include company_id |
| 401 | Invalid API key | Check HUDU_API_KEY |
| 404 | Website not found | Verify website ID |
| 422 | Validation failed | Check required fields |

### Validation Errors

| Error | Cause | Fix |
|-------|-------|-----|
| Name required | Missing name | Add domain name to request |
| Company required | No company_id | Include company_id |
| Invalid company | Bad company_id | Query /companies first |
| Duplicate domain | Domain already tracked | Check existing websites first |

### Error Recovery Pattern

```javascript
async function safeCreateWebsite(data) {
  try {
    return await createWebsite(data);
  } catch (error) {
    if (error.status === 422 && error.message?.includes('already')) {
      // Website already exists - find and return it
      const existing = await fetchWebsites({
        company_id: data.company_id,
        name: data.name
      });
      return existing[0];
    }

    throw error;
  }
}
```

## Best Practices

1. **Track all client domains** - Add every domain the client owns
2. **Monitor SSL certificates** - Set up alerts for expiring certificates
3. **Verify email security** - Check SPF, DKIM, DMARC for all domains
4. **Document hosting info** - Use notes to record hosting provider and account details
5. **Regular audits** - Verify website records match actual client domains quarterly
6. **Don't pause monitoring** - Keep monitoring active unless there is a specific reason to pause
7. **Track secondary domains** - Include marketing sites, client portals, subdomains
8. **DMARC enforcement** - Work toward "reject" policy for all client domains
9. **Link to credentials** - Cross-reference with Hudu passwords for domain registrar and hosting credentials
10. **Record DNS providers** - Note which DNS provider each domain uses

## Related Skills

- [Hudu Companies](../companies/SKILL.md) - Website company scope
- [Hudu Passwords](../passwords/SKILL.md) - Domain registrar and hosting credentials
- [Hudu Articles](../articles/SKILL.md) - DNS and hosting documentation
- [Hudu Assets](../assets/SKILL.md) - Server/hosting infrastructure
- [Hudu API Patterns](../api-patterns/SKILL.md) - API reference
