Building Federation Endpoints

Complete implementation guide for the 5 required AI endpoints with code examples, validation tips, and automation scripts.

Overview

To join the Digital Karma Web Federation, your site needs 5 JSON endpoints in the /ai/ directory:

Endpoint Complexity Update Frequency Build Time
manifest.json Easy On major changes 15 min
health.json Easy Every 5-15 minutes 20 min
catalog.json Moderate On content updates 45 min
karma.json Complex Daily 60 min
federation.json Easy When links change 20 min

Total implementation time: 2.5 to 4 hours for all 5 endpoints (including testing).

Prerequisites

1. Manifest Endpoint

Purpose: Declares site identity, capabilities, and federation version.

Path: /ai/manifest.json

Updates: Manually when site info changes

Minimal Example

{
  "name": "Your Site Name",
  "url": "https://yoursite.com",
  "description": "Brief description of your site's purpose",
  "federation_version": "6.1",
  "ai_endpoints": [
    "https://yoursite.com/ai/health.json",
    "https://yoursite.com/ai/catalog.json",
    "https://yoursite.com/ai/karma.json",
    "https://yoursite.com/ai/federation.json"
  ],
  "last_updated": "2026-03-05T10:00:00Z",
  "contact": {
    "type": "email",
    "value": "admin@yoursite.com"
  }
}

Full Example

{
  "name": "AI Website Systems",
  "url": "https://www.aiwebsitesystems.com",
  "description": "Comprehensive directory of AI-native website platforms with Digital Karma scoring",
  "federation_version": "6.1",
  "version": "1.0.0",
  "schema_types": ["Organization", "WebSite", "ItemList"],
  "ai_endpoints": [
    "https://www.aiwebsitesystems.com/ai/health.json",
    "https://www.aiwebsitesystems.com/ai/catalog.json",
    "https://www.aiwebsitesystems.com/ai/karma.json",
    "https://www.aiwebsitesystems.com/ai/federation.json"
  ],
  "capabilities": {
    "search": true,
    "filtering": true,
    "api": true
  },
  "primary_content_type": "directory",
  "categories": ["AI Platforms", "CMS", "Federation"],
  "last_updated": "2026-03-05T08:00:00Z",
  "contact": {
    "type": "email",
    "value": "hello@aiwebsitesystems.com"
  },
  "related_sites": [
    "https://seedstack.garden",
    "https://harvesthub.systems"
  ]
}

Implementation Steps

  1. Create /ai/ directory in your site root
  2. Create manifest.json file in that directory
  3. Fill in your site details (use template above)
  4. Set last_updated to current UTC timestamp (converter)
  5. Verify JSON validity (JSONLint)
  6. Upload to server
  7. Test: curl https://yoursite.com/ai/manifest.json

Validation

# Check manifest exists and is valid JSON
curl -I https://yoursite.com/ai/manifest.json
# Should return: 200 OK with Content-Type: application/json

# Validate required fields
curl https://yoursite.com/ai/manifest.json | jq '.name, .url, .federation_version'
# Should output your name, URL, and "6.1"

2. Health Endpoint

Purpose: Real-time site status and uptime metrics.

Path: /ai/health.json

Updates: Every 5-15 minutes (automated)

Minimal Example

{
  "status": "healthy",
  "last_check": "2026-03-05T10:15:00Z",
  "uptime_percent": 99.9,
  "response_time_ms": 120
}

Full Example

{
  "status": "healthy",
  "last_check": "2026-03-05T10:15:00Z",
  "uptime_percent": 99.92,
  "response_time_ms": 85,
  "metrics": {
    "total_pages": 47,
    "total_endpoints": 5,
    "last_content_update": "2026-03-04T18:30:00Z",
    "ssl_valid_until": "2027-01-15T00:00:00Z"
  },
  "endpoints_status": {
    "manifest": "operational",
    "catalog": "operational",
    "karma": "operational",
    "federation": "operational"
  },
  "server_info": {
    "platform": "Cloudflare Pages",
    "location": "Global CDN",
    "deployment": "2026-03-01T12:00:00Z"
  }
}

Implementation Options

Option A: Static JSON (Simple)

Good for sites with 99.9%+ uptime on reliable hosting.

  1. Create health.json manually
  2. Update via automated cron job or CI/CD
  3. Timestamp on each deployment

Option B: Dynamic Generation (Better)

Generate health.json on-demand with server-side script.

Python (Flask):

from flask import Flask, jsonify
from datetime import datetime
import os

app = Flask(__name__)

@app.route('/ai/health.json')
def health():
    last_modified = os.path.getmtime('./content/latest-post.md')
    
    return jsonify({
        "status": "healthy",
        "last_check": datetime.utcnow().isoformat() + 'Z',
        "uptime_percent": 99.9,
        "response_time_ms": 85,
        "metrics": {
            "total_pages": len(os.listdir('./content')),
            "last_content_update": datetime.fromtimestamp(last_modified).isoformat() + 'Z'
        }
    })

Node.js (Express):

const express = require('express');
const fs = require('fs');
const app = express();

app.get('/ai/health.json', (req, res) => {
  const stats = fs.statSync('./content/latest-post.md');
  
  res.json({
    status: 'healthy',
    last_check: new Date().toISOString(),
    uptime_percent: 99.9,
    response_time_ms: 85,
    metrics: {
      total_pages: fs.readdirSync('./content').length,
      last_content_update: stats.mtime.toISOString()
    }
  });
});

app.listen(3000);

PHP:

<?php
header('Content-Type: application/json');

$contentDir = './content';
$files = scandir($contentDir);
$latestFile = end($files);
$lastModified = filemtime($contentDir . '/' . $latestFile);

echo json_encode([
    'status' => 'healthy',
    'last_check' => gmdate('Y-m-d\TH:i:s\Z'),
    'uptime_percent' => 99.9,
    'response_time_ms' => 85,
    'metrics' => [
        'total_pages' => count($files) - 2, // subtract . and ..
        'last_content_update' => gmdate('Y-m-d\TH:i:s\Z', $lastModified)
    ]
], JSON_PRETTY_PRINT);
?>

Option C: Automated Script (Best)

Run a script via cron to update health.json every 15 minutes.

#!/usr/bin/env python3
import json
import os
from datetime import datetime

def update_health():
    # Count pages
    content_dir = './content'
    total_pages = len([f for f in os.listdir(content_dir) if f.endswith('.html')])
    
    # Get last update time
    files = [os.path.join(content_dir, f) for f in os.listdir(content_dir)]
    latest_file = max(files, key=os.path.getmtime)
    last_update = datetime.fromtimestamp(os.path.getmtime(latest_file))
    
    health = {
        "status": "healthy",
        "last_check": datetime.utcnow().isoformat() + 'Z',
        "uptime_percent": 99.9,
        "response_time_ms": 85,
        "metrics": {
            "total_pages": total_pages,
            "last_content_update": last_update.isoformat() + 'Z'
        }
    }
    
    with open('./ai/health.json', 'w') as f:
        json.dump(health, f, indent=2)
    
    print(f"Health updated at {datetime.now()}")

if __name__ == '__main__':
    update_health()

Cron job (runs every 15 minutes):

*/15 * * * * cd /path/to/your/site && python3 update_health.py

3. Catalog Endpoint

Purpose: Complete inventory of site content with metadata.

Path: /ai/catalog.json

Updates: When content changes (automated)

Structure

{
  "site_url": "https://yoursite.com",
  "generated_at": "2026-03-05T10:00:00Z",
  "total_items": 47,
  "categories": [
    {
      "name": "Category Name",
      "type": "Article | Product | Service | Event",
      "count": 12,
      "items": [
        {
          "id": "unique-id",
          "title": "Item Title",
          "url": "https://yoursite.com/path/to/item",
          "description": "Brief description",
          "type": "Article",
          "published": "2026-01-15T10:00:00Z",
          "updated": "2026-02-20T15:30:00Z",
          "schema_type": "Article",
          "tags": ["tag1", "tag2"]
        }
      ]
    }
  ]
}

Real Example (AI Website Systems)

{
  "site_url": "https://www.aiwebsitesystems.com",
  "generated_at": "2026-03-05T08:00:00Z",
  "total_items": 52,
  "categories": [
    {
      "name": "AI Platforms",
      "type": "SoftwareApplication",
      "count": 15,
      "items": [
        {
          "id": "digital-karma-static",
          "title": "Digital Karma Static Sites",
          "url": "https://www.aiwebsitesystems.com/directory-page.html#digital-karma-static",
          "description": "Federation v6.1 compliant static site generator",
          "type": "SoftwareApplication",
          "published": "2024-06-01T00:00:00Z",
          "updated": "2026-03-01T12:00:00Z",
          "schema_type": "SoftwareApplication",
          "tags": ["static", "federation", "schema-org"],
          "karma_score": 0.92,
          "endpoints": {
            "manifest": "https://example.com/ai/manifest.json"
          }
        }
      ]
    },
    {
      "name": "Knowledge Base",
      "type": "Article",
      "count": 10,
      "items": [
        {
          "id": "what-is-ai-website",
          "title": "What is an AI Website System?",
          "url": "https://www.aiwebsitesystems.com/knowledge-base/what-is-ai-website-system.html",
          "description": "Complete guide to AI-native website architecture",
          "type": "Article",
          "published": "2026-02-10T10:00:00Z",
          "updated": "2026-03-05T09:00:00Z",
          "schema_type": "Article",
          "tags": ["guide", "architecture", "ai-native"]
        }
      ]
    }
  ]
}

Generation Script (Python)

#!/usr/bin/env python3
import json
import os
import re
from datetime import datetime
from pathlib import Path

def extract_frontmatter(file_path):
    """Extract YAML frontmatter from Markdown/HTML file"""
    with open(file_path, 'r', encoding='utf-8') as f:
        content = f.read()
    
    # Look for YAML frontmatter (---)
    match = re.search(r'^---\n(.*?)\n---', content, re.DOTALL)
    if match:
        yaml_content = match.group(1)
        # Simple YAML parsing (use pyyaml for production)
        metadata = {}
        for line in yaml_content.split('\n'):
            if ':' in line:
                key, value = line.split(':', 1)
                metadata[key.strip()] = value.strip().strip('"\'')
        return metadata
    return {}

def generate_catalog():
    site_url = "https://yoursite.com"
    categories = {}
    
    # Scan content directory
    content_dir = Path('./content')
    for file_path in content_dir.rglob('*.md'):
        metadata = extract_frontmatter(file_path)
        
        category = metadata.get('category', 'Uncategorized')
        if category not in categories:
            categories[category] = []
        
        # File stats
        stats = os.stat(file_path)
        
        item = {
            "id": file_path.stem,
            "title": metadata.get('title', file_path.stem.replace('-', ' ').title()),
            "url": f"{site_url}/{file_path.relative_to(content_dir)}".replace('.md', '.html'),
            "description": metadata.get('description', ''),
            "type": metadata.get('type', 'Article'),
            "published": metadata.get('date', datetime.fromtimestamp(stats.st_ctime).isoformat()) + 'Z',
            "updated": datetime.fromtimestamp(stats.st_mtime).isoformat() + 'Z',
            "schema_type": metadata.get('schema_type', 'Article'),
            "tags": metadata.get('tags', '').split(',')
        }
        
        categories[category].append(item)
    
    # Build catalog structure
    catalog = {
        "site_url": site_url,
        "generated_at": datetime.utcnow().isoformat() + 'Z',
        "total_items": sum(len(items) for items in categories.values()),
        "categories": [
            {
                "name": name,
                "type": items[0]['type'] if items else 'Article',
                "count": len(items),
                "items": items
            }
            for name, items in categories.items()
        ]
    }
    
    # Write to file
    with open('./ai/catalog.json', 'w') as f:
        json.dump(catalog, f, indent=2)
    
    print(f"Catalog generated: {catalog['total_items']} items in {len(categories)} categories")

if __name__ == '__main__':
    generate_catalog()

Automation

Regenerate catalog when content changes:

# Git hook (post-commit)
#!/bin/bash
python3 update_catalog.py
git add ai/catalog.json
git commit --amend --no-edit

# Or CI/CD (GitHub Actions)
name: Update Catalog
on:
  push:
    paths:
      - 'content/**'
jobs:
  update:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Generate catalog
        run: python3 update_catalog.py
      - name: Commit changes
        run: |
          git config --global user.name 'Bot'
          git add ai/catalog.json
          git commit -m "Update catalog" || true
          git push

4. Karma Endpoint

Purpose: Digital Karma Score and signal breakdown.

Path: /ai/karma.json

Updates: Daily (automated)

Structure

{
  "score": 0.87,
  "badge": "Karma Pro",
  "tier": "pro",
  "last_calculated": "2026-03-05T08:00:00Z",
  "signals": {
    "schema_coverage": 0.90,
    "content_freshness": 1.00,
    "ai_endpoints": 1.00,
    "federation_presence": 0.80,
    "external_links": 0.70,
    "technical_quality": 0.90,
    "dataset_quality": 0.70
  },
  "details": {
    "schema_coverage": {
      "score": 0.90,
      "pages_with_schema": 45,
      "total_pages": 50
    },
    "content_freshness": {
      "score": 1.00,
      "days_since_update": 1,
      "last_update": "2026-03-04T18:30:00Z"
    }
  }
}

Calculation Script (Python)

Full Digital Karma calculator:

#!/usr/bin/env python3
import json
import os
from datetime import datetime, timedelta
from pathlib import Path
import re

def calculate_karma():
    """Calculate complete Digital Karma Score"""
    
    # 1. Schema Coverage (20%)
    schema_score = calculate_schema_coverage()
    
    # 2. Content Freshness (15%)
    freshness_score = calculate_content_freshness()
    
    # 3. AI Endpoints (25%)
    endpoints_score = calculate_endpoints()
    
    # 4. Federation Presence (15%)
    federation_score = calculate_federation()
    
    # 5. External Links (10%)
    links_score = calculate_external_links()
    
    # 6. Technical Quality (10%)
    technical_score = calculate_technical_quality()
    
    # 7. Dataset Quality (5%)
    dataset_score = calculate_dataset_quality()
    
    # Weighted average
    total_score = (
        schema_score * 0.20 +
        freshness_score * 0.15 +
        endpoints_score * 0.25 +
        federation_score * 0.15 +
        links_score * 0.10 +
        technical_score * 0.10 +
        dataset_score * 0.05
    )
    
    # Determine badge
    if total_score >= 0.95:
        badge = "Karma Elite"
        tier = "elite"
    elif total_score >= 0.85:
        badge = "Karma Pro"
        tier = "pro"
    elif total_score >= 0.70:
        badge = "Karma Bronze"
        tier = "bronze"
    else:
        badge = "No Badge"
        tier = "none"
    
    karma = {
        "score": round(total_score, 3),
        "badge": badge,
        "tier": tier,
        "last_calculated": datetime.utcnow().isoformat() + 'Z',
        "signals": {
            "schema_coverage": round(schema_score, 2),
            "content_freshness": round(freshness_score, 2),
            "ai_endpoints": round(endpoints_score, 2),
            "federation_presence": round(federation_score, 2),
            "external_links": round(links_score, 2),
            "technical_quality": round(technical_score, 2),
            "dataset_quality": round(dataset_score, 2)
        }
    }
    
    with open('./ai/karma.json', 'w') as f:
        json.dump(karma, f, indent=2)
    
    print(f"Karma calculated: {badge} ({total_score:.3f})")
    return karma

def calculate_schema_coverage():
    """Check Schema.org JSON-LD in HTML pages"""
    html_files = list(Path('.').rglob('*.html'))
    pages_with_schema = 0
    
    for file in html_files:
        content = file.read_text(encoding='utf-8')
        if 'application/ld+json' in content:
            pages_with_schema += 1
    
    if not html_files:
        return 0.0
    
    coverage = pages_with_schema / len(html_files)
    return min(coverage, 1.0)

def calculate_content_freshness():
    """Check recency of last content update"""
    content_files = list(Path('./content').rglob('*.*'))
    if not content_files:
        return 0.0
    
    latest_mtime = max(os.path.getmtime(f) for f in content_files)
    days_old = (datetime.now() - datetime.fromtimestamp(latest_mtime)).days
    
    if days_old == 0:
        return 1.0
    elif days_old <= 7:
        return 0.95
    elif days_old <= 30:
        return 0.85
    elif days_old <= 90:
        return 0.70
    elif days_old <= 180:
        return 0.50
    else:
        return 0.30

def calculate_endpoints():
    """Check AI endpoints exist and are valid"""
    endpoints = ['manifest.json', 'health.json', 'catalog.json', 'karma.json', 'federation.json']
    existing = sum(1 for e in endpoints if Path(f'./ai/{e}').exists())
    return existing / len(endpoints)

def calculate_federation():
    """Check federation.json links"""
    federation_path = Path('./ai/federation.json')
    if not federation_path.exists():
        return 0.0
    
    with open(federation_path) as f:
        data = json.load(f)
    
    links = len(data.get('federation_links', []))
    
    if links >= 10:
        return 1.0
    elif links >= 5:
        return 0.85
    elif links >= 3:
        return 0.70
    elif links >= 1:
        return 0.50
    else:
        return 0.0

def calculate_external_links():
    """Count quality external links"""
    # Simplified - in production, check link quality
    html_files = list(Path('.').rglob('*.html'))
    total_external = 0
    
    for file in html_files:
        content = file.read_text(encoding='utf-8')
        # Count external links (basic pattern)
        external = len(re.findall(r'href="https?://(?!yoursite.com)', content))
        total_external += external
    
    if total_external >= 50:
        return 1.0
    elif total_external >= 30:
        return 0.85
    elif total_external >= 15:
        return 0.70
    elif total_external >= 5:
        return 0.50
    else:
        return 0.30

def calculate_technical_quality():
    """Check technical implementation"""
    score = 0.0
    
    # HTTPS (check by environment or config)
    score += 0.30
    
    # Performance (check file sizes)
    html_files = list(Path('.').rglob('*.html'))
    avg_size = sum(f.stat().st_size for f in html_files) / len(html_files) if html_files else 0
    if avg_size < 100_000:  # < 100KB
        score += 0.35
    elif avg_size < 200_000:
        score += 0.25
    
    # Accessibility (check for alt tags, aria labels)
    pages_with_alt = 0
    for file in html_files:
        content = file.read_text(encoding='utf-8')
        if 'alt=' in content:
            pages_with_alt += 1
    if html_files:
        score += (pages_with_alt / len(html_files)) * 0.35
    
    return min(score, 1.0)

def calculate_dataset_quality():
    """Check structured datasets"""
    datasets_dir = Path('./datasets')
    if not datasets_dir.exists():
        return 0.5  # Minimum for having endpoints
    
    datasets = list(datasets_dir.glob('*.json'))
    if len(datasets) >= 3:
        return 1.0
    elif len(datasets) >= 2:
        return 0.85
    elif len(datasets) >= 1:
        return 0.70
    else:
        return 0.50

if __name__ == '__main__':
    calculate_karma()

Full scoring methodology: Digital Karma Scoring Guide

5. Federation Endpoint

Purpose: List of bidirectional federation links.

Path: /ai/federation.json

Updates: When adding/removing partners

Structure

{
  "site_url": "https://yoursite.com",
  "federation_version": "6.1",
  "last_updated": "2026-03-05T10:00:00Z",
  "federation_links": [
    {
      "site_url": "https://partner-site.com",
      "site_name": "Partner Site Name",
      "relationship": "partner",
      "verified": true,
      "added_date": "2026-01-15T00:00:00Z",
      "karma_score": 0.82,
      "description": "Brief description of the partner"
    }
  ],
  "total_links": 5,
  "network_stats": {
    "avg_karma": 0.85,
    "total_network_sites": 12
  }
}

Example

{
  "site_url": "https://www.aiwebsitesystems.com",
  "federation_version": "6.1",
  "last_updated": "2026-03-05T08:00:00Z",
  "federation_links": [
    {
      "site_url": "https://seedstack.garden",
      "site_name": "SeedStack Garden",
      "relationship": "partner",
      "verified": true,
      "added_date": "2025-06-10T00:00:00Z",
      "karma_score": 0.85,
      "description": "Digital publication on sustainable living practices"
    },
    {
      "site_url": "https://harvesthub.systems",
      "site_name": "HarvestHub Systems",
      "relationship": "partner",
      "verified": true,
      "added_date": "2025-08-22T00:00:00Z",
      "karma_score": 0.88,
      "description": "Platform for regenerative agriculture data"
    }
  ],
  "total_links": 7,
  "network_stats": {
    "avg_karma": 0.87,
    "total_network_sites": 15
  }
}

Adding Federation Partners

  1. Identify potential partners in Federation Registry
  2. Reach out via their contact info in manifest.json
  3. Add them to your federation.json with verified: false
  4. Send them your manifest link to add you back
  5. Once mutual links exist, set verified: true
  6. Update karma scores periodically

Testing & Validation

Manual Testing

# Test all endpoints exist
curl -I https://yoursite.com/ai/manifest.json
curl -I https://yoursite.com/ai/health.json
curl -I https://yoursite.com/ai/catalog.json
curl -I https://yoursite.com/ai/karma.json
curl -I https://yoursite.com/ai/federation.json

# Validate JSON syntax
curl https://yoursite.com/ai/manifest.json | jq .
curl https://yoursite.com/ai/health.json | jq .
curl https://yoursite.com/ai/catalog.json | jq .
curl https://yoursite.com/ai/karma.json | jq .
curl https://yoursite.com/ai/federation.json | jq .

# Check required fields
curl https://yoursite.com/ai/manifest.json | jq '.name, .url, .federation_version'

Automated Validation

Use our validators from Developer Tools:

# Node.js validator
npx @aiwebsites/validate https://yoursite.com

# Python validator
pip install aiwebsites-validator
aiwebsites-validate https://yoursite.com

Deployment Checklist

Next Steps

  1. Read Full Federation Specification
  2. Use Developer Tools & Validators
  3. Add Schema.org Markup
  4. Understand Karma Scoring
  5. Submit to Join Federation