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
- ✅ Your site is live with a domain
- ✅ HTTPS enabled (required)
- ✅ Ability to create/edit JSON files
- ✅ Basic command-line or scripting knowledge
- ✅ (Optional) Python 3.7+ or Node.js for automation
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
- Create
/ai/directory in your site root - Create
manifest.jsonfile in that directory - Fill in your site details (use template above)
- Set
last_updatedto current UTC timestamp (converter) - Verify JSON validity (JSONLint)
- Upload to server
- 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.
- Create health.json manually
- Update via automated cron job or CI/CD
- 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
- Identify potential partners in Federation Registry
- Reach out via their contact info in manifest.json
- Add them to your federation.json with
verified: false - Send them your manifest link to add you back
- Once mutual links exist, set
verified: true - 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
- ☐ All 5 endpoints created in
/ai/directory - ☐ JSON syntax validated with JSONLint or jq
- ☐ All URLs use HTTPS (not HTTP)
- ☐ Timestamps in ISO 8601 UTC format
- ☐ Manifest includes all required fields
- ☐ Health endpoint updates automatically
- ☐ Catalog includes all site content
- ☐ Karma score calculated correctly
- ☐ Federation links are bidirectional
- ☐ Content-Type headers set to application/json
- ☐ CORS enabled if needed for API access
- ☐ Tested with curl/browser
- ☐ Run official validator
- ☐ Submit to federation: Submit Form