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:
- Prevents Chaos - Accidental or malicious changes corrupt infrastructure
- Enforces Validation - API validates everything before applying
- Maintains Audit Trail - Every change is logged and tracked
- Enables Rollback - Changes go through git → easy to revert
- 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