Skip to content

Complete API Guide for Agents & Operators

For Coding Agents: Use the API, Don't Modify Files Directly

This guide explains why you should use the REST API and how to do everything correctly.


🤖 Why You Can't Modify Files Directly

Files are read-only by design:

$ echo "new config" > /opt/charliehub/traefik/config/dynamic/static-routes.yml
bash: static-routes.yml: Permission denied

This is intentional. Here's why:

  1. Prevents Chaos - Accidental or malicious changes corrupt infrastructure
  2. Enforces Validation - API validates everything before applying
  3. Maintains Audit Trail - Every change is logged and tracked
  4. Enables Rollback - Changes go through git → easy to revert
  5. Separates Concerns - Code that uses API vs code that modifies files directly

The API is your only valid interface for domain management.


✅ How to Do Everything via API

1. Add a New Domain

#!/bin/bash
# Script for agents to add a new domain

API_KEY="your_api_key_here"
API_HOST="http://172.19.0.5:8001"

# Example: Add a new service
curl -X POST "$API_HOST/api/domains" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "myservice.charliehub.net",
    "service_type": "http",
    "environment": "production",
    "backend_host": "10.44.1.100",
    "backend_port": 8080,
    "cors_enabled": false,
    "auth_required": true
  }'

# Response:
# {"success":true,"message":"Domain myservice.charliehub.net created successfully"}

2. Update an Existing Domain

#!/bin/bash
# Change a domain's backend or settings

API_KEY="your_api_key_here"
API_HOST="http://172.19.0.5:8001"
DOMAIN_ID="42"

curl -X PUT "$API_HOST/api/domains/$DOMAIN_ID" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "myservice.charliehub.net",
    "service_type": "http",
    "environment": "production",
    "backend_host": "10.44.1.200",
    "backend_port": 9000,
    "cors_enabled": false,
    "auth_required": true
  }'

3. Make a Domain Public (No Auth)

#!/bin/bash
# Disable Authelia authentication for a public domain

curl -X PUT "$API_HOST/api/domains/$DOMAIN_ID" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "domain": "public.charliehub.net",
    "service_type": "http",
    "environment": "production",
    "backend_host": "10.44.1.100",
    "backend_port": 8080,
    "cors_enabled": false,
    "auth_required": false
  }'

# Now: 🔓 Domain is public, no Authelia required
# Still: Security headers applied (CSP, X-Frame-Options, etc.)

4. Deploy Changes to Production

#!/bin/bash
# After creating/updating domains, deploy them

curl -X POST "$API_HOST/api/deploy-all" \
  -H "X-API-Key: $API_KEY"

# This triggers:
# 1. Traefik config regeneration
# 2. OVH DNS updates
# 3. Let's Encrypt certificate provisioning
# 4. Health check initialization

5. List All Domains

#!/bin/bash
# See all configured domains

curl -s -H "X-API-Key: $API_KEY" \
  "$API_HOST/api/domains" | jq '.[] | {domain, auth_required, backend_host}'

# Output:
# {
#   "domain": "control.trevarn.com",
#   "auth_required": false,
#   "backend_host": "10.44.1.214"
# }

6. Get Details of One Domain

#!/bin/bash
# Check configuration of specific domain

curl -s -H "X-API-Key: $API_KEY" \
  "$API_HOST/api/domains/24" | jq .

# Shows all fields, status, health checks, etc.

7. Delete a Domain

#!/bin/bash
# Remove a domain (careful!)

curl -X DELETE "$API_HOST/api/domains/24" \
  -H "X-API-Key: $API_KEY"

# This also:
# - Removes from OVH DNS
# - Removes from Traefik routing
# - Stops certificate renewal

🔑 Getting Your API Key

The API key is stored in the domain-manager container environment:

# Ask your admin for the API key
# DO NOT hardcode it in scripts

# Better: Store in environment
export DOMAIN_MANAGER_API_KEY="YOUR_API_KEY_HERE"
curl -H "X-API-Key: $DOMAIN_MANAGER_API_KEY" \
  "http://172.19.0.5:8001/api/domains"

# Even better: Use a .env file (git-ignored)
# .env:
# DOMAIN_MANAGER_API_KEY=your_key_here
#
# Script:
# source .env
# curl -H "X-API-Key: $DOMAIN_MANAGER_API_KEY" ...

📝 Complete Python Example

#!/usr/bin/env python3
"""
Domain Manager API Client
For agents to interact with domain configuration
"""

import requests
import os
from typing import Dict, Any, Optional

class DomainManagerClient:
    def __init__(self, api_host: str = "http://172.19.0.5:8001",
                 api_key: str = None):
        self.api_host = api_host
        self.api_key = api_key or os.getenv('DOMAIN_MANAGER_API_KEY')
        if not self.api_key:
            raise ValueError("API_KEY required: set DOMAIN_MANAGER_API_KEY env var")
        self.headers = {
            'X-API-Key': self.api_key,
            'Content-Type': 'application/json'
        }

    def create_domain(self, domain: str, backend_host: str,
                     backend_port: int, service_type: str = 'http',
                     auth_required: bool = True) -> Dict[str, Any]:
        """Create a new domain"""
        payload = {
            'domain': domain,
            'service_type': service_type,
            'environment': 'production',
            'backend_host': backend_host,
            'backend_port': backend_port,
            'cors_enabled': False,
            'auth_required': auth_required
        }
        response = requests.post(
            f'{self.api_host}/api/domains',
            json=payload,
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()

    def update_domain(self, domain_id: int, **kwargs) -> Dict[str, Any]:
        """Update a domain"""
        response = requests.put(
            f'{self.api_host}/api/domains/{domain_id}',
            json=kwargs,
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()

    def list_domains(self) -> list:
        """List all domains"""
        response = requests.get(
            f'{self.api_host}/api/domains',
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()

    def get_domain(self, domain_id: int) -> Dict[str, Any]:
        """Get details of one domain"""
        response = requests.get(
            f'{self.api_host}/api/domains/{domain_id}',
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()

    def delete_domain(self, domain_id: int) -> Dict[str, Any]:
        """Delete a domain"""
        response = requests.delete(
            f'{self.api_host}/api/domains/{domain_id}',
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()

    def deploy_all(self) -> Dict[str, Any]:
        """Deploy all changes to production"""
        response = requests.post(
            f'{self.api_host}/api/deploy-all',
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()

# Usage Example
if __name__ == '__main__':
    client = DomainManagerClient()

    # List all domains
    domains = client.list_domains()
    print(f"Total domains: {len(domains)}")

    # Create a new domain
    result = client.create_domain(
        domain='myapp.charliehub.net',
        backend_host='10.44.1.150',
        backend_port=3000,
        service_type='spa',
        auth_required=True
    )
    print(f"Created: {result}")

    # Deploy changes
    deploy = client.deploy_all()
    print(f"Deployed: {deploy['message']}")

⚠️ Common Mistakes to Avoid

❌ Don't Do This

# ❌ WRONG: Trying to modify files directly
$ nano /opt/charliehub/traefik/config/dynamic/static-routes.yml
$ sed -i 's/auth_required: true/auth_required: false/' static-routes.yml
# Results in: Permission denied (read-only)

# ❌ WRONG: Connecting directly to database
$ psql -h 127.0.0.1 -U charliehub -d charliehub_domains
# Results in: Connection refused (blocked by firewall)

# ❌ WRONG: Hardcoding API key in script
export API_KEY="YOUR_API_KEY_HERE"
git add my_script.sh
# Now the key is in git history forever ❌

# ❌ WRONG: Using API without Content-Type header
curl -X POST http://api/domains -H "X-API-Key: key" -d '{"domain":"test"}'
# Results in: 422 Unprocessable Entity

✅ Do This Instead

# ✅ CORRECT: Use the API
curl -X POST http://172.19.0.5:8001/api/domains \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"domain":"test","backend_host":"10.44.1.100","backend_port":8080,...}'

# ✅ CORRECT: Store API key in env
export DOMAIN_MANAGER_API_KEY="YOUR_API_KEY_HERE"  # In .env or shell init
source ~/.bashrc
curl -H "X-API-Key: $DOMAIN_MANAGER_API_KEY" http://api/domains

# ✅ CORRECT: Never commit secrets
echo "API_KEY=secret" >> .env
echo ".env" >> .gitignore
git commit  # .env is never committed

🔍 Checking If Your Change Worked

#!/bin/bash
# After deploying, verify everything

API_HOST="http://172.19.0.5:8001"
API_KEY="$DOMAIN_MANAGER_API_KEY"

# 1. Check domain exists in database
curl -s -H "X-API-Key: $API_KEY" "$API_HOST/api/domains" | \
  jq '.[] | select(.domain=="myservice.charliehub.net")'

# 2. Check Traefik has the route
curl -s http://127.0.0.1:8091/api/http/routers | \
  jq '.[] | select(.name | contains("myservice"))'

# 3. Check DNS resolves
dig myservice.charliehub.net +short

# 4. Test the actual service
curl -v https://myservice.charliehub.net/
# Should return 200 OK (or redirect if not authenticated)

📞 Troubleshooting

API Returns 401 Unauthorized

Problem: Invalid API key
Solution: Check that X-API-Key header is present and correct
          export DOMAIN_MANAGER_API_KEY=<correct_key>

API Returns 422 Unprocessable Entity

Problem: Missing Content-Type or invalid JSON
Solution: Add -H "Content-Type: application/json"
          Validate JSON: echo '{"domain":"test"}' | jq .

API Returns 409 Conflict

Problem: Domain already exists
Solution: Use domain_id to UPDATE instead of POST
          Or choose a different domain name

Changes Don't Appear in Traefik

Problem: Forgot to call /api/deploy-all
Solution: After creating/updating domains:
          curl -X POST $API_HOST/api/deploy-all -H "X-API-Key: $API_KEY"

Domain is unreachable

Problem: Backend service not responding
Solution: Check backend_host and backend_port are correct
          Verify backend service is actually running
          Check health checks in domain details

✨ Summary

  • Use the API for everything - it's the only supported way
  • Store API key in environment - never commit it
  • Validate JSON before sending - use jq for testing
  • Call deploy-all after changes - to apply to Traefik
  • Check results - verify domain exists and Traefik has the route
  • Never modify files directly - they're read-only by design
  • Never access database directly - ports are blocked

The API is simple, validated, and audited. Use it.


Last updated: 2026-02-08 Version: 1.0 For questions: Check OPERATOR_HOWTO.md