{"openapi":"3.1.0","info":{"title":"CharlieHub Domain Manager","description":"Automated domain management with Traefik and DDNS integration","version":"3.0.0"},"paths":{"/api/email/aliases":{"get":{"tags":["Email"],"summary":"List send-as aliases","description":"Get list of verified Gmail send-as aliases","operationId":"get_aliases_api_email_aliases_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AliasesResponse"}}}}}}},"/api/email/send":{"post":{"tags":["Email"],"summary":"Send email","description":"Send email via Gmail API.\n\nThe from_address must be a verified send-as alias in Gmail.\nUse GET /api/email/aliases to see available addresses.","operationId":"send_email_api_email_send_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/email/refresh-token":{"post":{"tags":["Email"],"summary":"Refresh OAuth token","description":"Manually refresh the Gmail OAuth token","operationId":"refresh_token_api_email_refresh_token_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/email/health":{"get":{"tags":["Email"],"summary":"Email service health","description":"Check email service health","operationId":"email_health_api_email_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/network":{"get":{"tags":["network"],"summary":"Network Page","operationId":"network_page_network_get","responses":{"200":{"description":"Successful Response","content":{"text/html":{"schema":{"type":"string"}}}}}}},"/api/network/summary":{"get":{"tags":["network"],"summary":"Get Network Summary","description":"Network health summary: per-site WAN, WireGuard, client counts from lan_hosts.","operationId":"get_network_summary_api_network_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/network/lan-hosts":{"get":{"tags":["network"],"summary":"Get Lan Hosts","description":"All LAN devices from lan_hosts — every client visible on the SD-WAN.","operationId":"get_lan_hosts_api_network_lan_hosts_get","parameters":[{"name":"site","in":"query","required":false,"schema":{"type":"string","title":"Site"}},{"name":"online_only","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Online Only"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ssh-clients":{"get":{"tags":["Services"],"summary":"List SSH clients","description":"List all authorized SSH clients for sshpiper","operationId":"list_ssh_clients_api_ssh_clients_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"tags":["Services"],"summary":"Add SSH client","description":"Add a new authorized SSH client public key","operationId":"add_ssh_client_api_ssh_clients_post","requestBody":{"content":{"application/json":{"schema":{"type":"object","title":"Data"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["Services"],"summary":"Remove SSH client","description":"Remove an authorized SSH client by key substring match","operationId":"remove_ssh_client_api_ssh_clients_delete","requestBody":{"content":{"application/json":{"schema":{"type":"object","title":"Data"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/dns/zones":{"get":{"summary":"DNS zones","description":"Get available DNS zones from OVH API","operationId":"dns_zones_api_dns_zones_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DNSZonesResponse"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/metrics/summary":{"get":{"summary":"Metrics Summary","description":"Human-readable metrics summary for dashboards\nRequires authentication","operationId":"metrics_summary_api_metrics_summary_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/discover":{"get":{"tags":["Services"],"summary":"Discover services","description":"Discover all services on hub1 and homelab, match to domains","operationId":"discover_services_api_discover_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/discover/hub1":{"get":{"tags":["Services"],"summary":"Discover hub1 services","description":"Discover Docker containers on hub1","operationId":"discover_hub1_api_discover_hub1_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/discover/homelab":{"get":{"tags":["Services"],"summary":"Discover homelab services","description":"Discover network clients from UniFi API","operationId":"discover_homelab_api_discover_homelab_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/ddns/update/{domain}":{"get":{"tags":["Services"],"summary":"Remote DDNS update","description":"Update DNS A record for a domain.\n\n    Accepts two auth modes:\n    - **Global API key**: full access, any domain (admin / WAN Watcher use)\n    - **Scoped site token**: bound to exactly one VPN hostname (UCG cron use)\n\n    A scoped token that does not match the requested domain is rejected with 403.\n\n    Usage (UCG cron):\n    ```\n    curl \"https://domains.charliehub.net/api/ddns/update/fr-vpn.charliehub.net?key=TOKEN&ip=1.2.3.4\"\n    ```\n    The `?ip=` parameter is required when calling from a UCG — X-Forwarded-For\n    would carry the WireGuard tunnel IP, not the real WAN IP.","operationId":"ddns_update_api_ddns_update__domain__get","parameters":[{"name":"domain","in":"path","required":true,"schema":{"type":"string","title":"Domain"}},{"name":"key","in":"query","required":false,"schema":{"type":"string","title":"Key"}},{"name":"ip","in":"query","required":false,"schema":{"type":"string","title":"Ip"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/domains":{"get":{"summary":"List all domains","description":"Get all domains from the database with full details","operationId":"list_domains_api_domains_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/DomainResponse"},"type":"array","title":"Response List Domains Api Domains Get"}}}}},"security":[{"APIKeyHeader":[]}]},"post":{"summary":"Create domain","description":"Add a new domain to the system","operationId":"create_domain_api_domains_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainCreate"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/domains/stats":{"get":{"tags":["Domains"],"summary":"Get domain statistics","description":"Get counts of domains by status and other metrics","operationId":"get_domain_stats_api_domains_stats_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/prometheus-targets":{"get":{"tags":["Domains"],"summary":"Prometheus HTTP SD targets","description":"Returns active HTTP-probeable domains in Prometheus HTTP Service Discovery format. No auth required.","operationId":"prometheus_targets_api_prometheus_targets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/domains/by-name/{domain_name}":{"get":{"tags":["Domains"],"summary":"Get domain identity by FQDN","description":"Fetch identity fields for a single domain by fully-qualified domain name. Returns a narrow DomainIdentityResponse: protocol, service_type, status, external_ip, external_port only. No infrastructure topology fields. Used by monitoring agents (e.g. wg-monitor) for least-privilege read access.","operationId":"get_domain_by_name_api_domains_by_name__domain_name__get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"domain_name","in":"path","required":true,"schema":{"type":"string","title":"Domain Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainIdentityResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/domains/render":{"post":{"tags":["Domains"],"summary":"Render Traefik config (preview only)","description":"Renders the full Traefik dynamic configuration from the current DB state without writing to disk. Returns the YAML content, checksum, router counts, and apply_order. Output is deterministic: PyYAML sort_keys=True + DB ORDER BY domain ensures identical DB state always produces identical YAML bytes.","operationId":"render_domains_api_domains_render_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/domains/drift":{"get":{"tags":["Domains"],"summary":"Content-based drift check (routes.yml vs DB)","description":"Renders config from DB, computes SHA-256, and compares against the on-disk routes.yml. routes.yml is always generator-owned — any drift is an invariant violation.","operationId":"check_domain_drift_api_domains_drift_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/domains/{domain_id}":{"get":{"summary":"Get single domain","description":"Get details for a specific domain by ID","operationId":"get_domain_api_domains__domain_id__get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"summary":"Update domain","description":"Update an existing domain configuration","operationId":"update_domain_api_domains__domain_id__put","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DomainUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete domain","description":"Remove a domain from the system","operationId":"delete_domain_api_domains__domain_id__delete","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/domains/{domain_id}/activate":{"post":{"tags":["Domains"],"summary":"Activate domain","description":"ONLY legal path to activate a domain. Handles first-time activation (create DNS) and idempotent redeploy.","operationId":"activate_domain_api_domains__domain_id__activate_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeploymentResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/domains/{domain_id}/disable":{"post":{"tags":["Domains"],"summary":"Disable domain","description":"ONLY legal path to disable a domain. Sets status='disabled' (terminal, no resurrection).","operationId":"disable_domain_api_domains__domain_id__disable_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/domains/{domain_id}/deactivate":{"post":{"tags":["Domains"],"summary":"Deactivate domain (non-terminal)","description":"Transitions active→inactive. Unlike /disable (terminal), deactivated domains can be reactivated via /activate. DNS records are preserved; only Traefik routing is removed (inactive domains are excluded from routes.yml).","operationId":"deactivate_domain_api_domains__domain_id__deactivate_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/domains/{domain_id}/autodetect":{"post":{"tags":["Domains"],"summary":"Auto-detect service type","description":"Probes the domain's backend and returns a service type suggestion. READ-ONLY: does not modify the domain, revision, status, or any lifecycle field.","operationId":"autodetect_service_type_api_domains__domain_id__autodetect_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutodetectRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/domains/{domain_id}/deploy":{"post":{"summary":"Deploy single domain","description":"Deploy a specific domain to Traefik and DDNS","operationId":"deploy_domain_api_domains__domain_id__deploy_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeploymentResult"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/deploy-all":{"post":{"summary":"Deploy all domains","description":"Deploy all active domains to Traefik and DDNS","operationId":"deploy_all_api_deploy_all_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeploymentResult"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/dns/status":{"get":{"tags":["Domains"],"summary":"DNS drift check","description":"Compare routes.yml hosts against deployed dnsmasq config","operationId":"dns_status_api_dns_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/dns/integrity":{"get":{"tags":["Domains"],"summary":"DNS integrity state","description":"Last result from the systemd charliehub-dns-integrity timer","operationId":"dns_integrity_api_dns_integrity_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/deploy-traefik":{"post":{"tags":["Domains"],"summary":"Deploy Traefik configuration","description":"Generate and deploy Traefik dynamic configuration","operationId":"deploy_traefik_api_deploy_traefik_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/domains/expose":{"post":{"tags":["Domains"],"summary":"Quick expose service","description":"One-click service exposure. Creates domain record, updates DNS, and regenerates Traefik config.\n\n    This endpoint:\n    1. Creates a domain record with static_ip=51.68.235.106 (hub2)\n    2. Updates DNS A record via OVH API\n    3. Regenerates Traefik config with appropriate middlewares\n    4. Applies Authelia SSO if auth_required=true","operationId":"expose_service_api_domains_expose_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExposeServiceRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/tls/reconcile":{"post":{"tags":["Domains"],"summary":"Trigger internal cert issuance","description":"Call cert-manager /reconcile synchronously, then regenerate internal-certs.yml. Idempotent.","operationId":"tls_reconcile_api_tls_reconcile_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/services":{"get":{"tags":["Domains"],"summary":"List Services","description":"Return all service groups: service_key -> list of associated domains.","operationId":"list_services_api_services_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/middlewares":{"get":{"tags":["Diagnostics"],"summary":"Middleware catalog","description":"Returns all HTTP middlewares defined in core/*.yml, enriched with live Traefik load status. Use this to discover valid names for middleware_config.extra_middlewares.","operationId":"list_middlewares_api_middlewares_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/domains/{domain_id}/cert":{"get":{"tags":["Diagnostics"],"summary":"TLS certificate status","description":"Reports what TLS certificate is currently being served on port 443 for this domain. This reflects the live TLS handshake — it does NOT expose ACME pipeline state (challenge status, retry backoff, failure reason). Internal-only domains will show reachable=false — this is expected. managed_by_acme: true if this domain has a certResolver in routes.yml (null if unknown). in_acme_store: whether the domain appears in Traefik's acme.json store (null if store unavailable).","operationId":"get_cert_status_api_domains__domain_id__cert_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/domains/{domain_id}/effective-config":{"get":{"tags":["Diagnostics"],"summary":"Effective configuration (DB + generated + runtime)","description":"Returns a correlated view of: (1) what the DB intends for this domain, (2) what was generated in routes.yml, (3) what Traefik has loaded at runtime. Use sync_status to determine if a 404 is a DB issue, generation issue, or reload issue. Pass ?check_backend=true to also TCP-probe the backend host:port.","operationId":"get_effective_config_api_domains__domain_id__effective_config_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"domain_id","in":"path","required":true,"schema":{"type":"integer","title":"Domain Id"}},{"name":"check_backend","in":"query","required":false,"schema":{"type":"boolean","description":"TCP-probe the backend host:port (backend_tcp_reachable)","default":false,"title":"Check Backend"},"description":"TCP-probe the backend host:port (backend_tcp_reachable)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/deploy/status":{"get":{"tags":["Diagnostics"],"summary":"Last deploy metadata","description":"Returns metadata from the most recent deploy-all operation: timestamp, success flag, domain count, routes.yml hash, and any warnings. Use this instead of inspecting Traefik logs to confirm a deploy took effect. source=memory: process has not restarted since last deploy. source=db: read from persistent DB (survives restarts). source=file: last resort file mtime only — success/mode/count unknown. fallback=true means source=file (legacy field, kept for compatibility).","operationId":"get_deploy_status_api_deploy_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/acme/status":{"get":{"tags":["Diagnostics"],"summary":"ACME store status","description":"Reports the state of Traefik's acme.json certificate store, read via Docker archive API (no exec, no shell — read-only tar download). Returns resolver list, cert count, account registration status, and file metadata (mtime, mode, owner) from the tar header. acme_storage_present=false means acme.json was not found in the Traefik container. error=acme_store_temporarily_unreadable means Traefik is mid-write; retry shortly.","operationId":"get_acme_status_api_acme_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/authelia/drift":{"get":{"tags":["Authelia"],"summary":"Authelia ↔ Traefik ↔ DB drift report","description":"Read-only three-way drift detection. Compares the database's auth_required flag against Authelia's access_control.rules and Traefik's middleware attachments. Returns ok=true iff all drift lists are empty.","operationId":"authelia_drift_api_authelia_drift_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/control-plane/health":{"get":{"tags":["Control Plane"],"summary":"Global control plane health (aggregated)","description":"Read-only assertion that all generated control-plane systems (Authelia, Traefik, docs) match their declared state and are actively enforcing it. Returns ok=true iff every component reports ok, no component is in drift (or unknown-drift), and all DB-level invariants hold. The drift field is three-state: false=verified clean, true=verified dirty, null=unknown (treated as dirty for global ok). One component failing never hides the others — the full breakdown is always returned.","operationId":"control_plane_health_api_control_plane_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/users/invite":{"post":{"tags":["Users"],"summary":"Invite User","description":"Provision a new Authelia user and send an invite email.\n\nFlow:\n1. Generate unique username from suggestion\n2. Generate random temp password (user will reset via Authelia password-reset)\n3. Hash password via Authelia CLI (argon2id)\n4. Write user to users_database.yml (full rebuild, atomic)\n5. Restart Authelia to load new user\n6. Send invite email with instructions","operationId":"invite_user_api_users_invite_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InviteResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/users/{username}/revoke":{"post":{"tags":["Users"],"summary":"Revoke User","description":"Disable an Authelia user (sets disabled=true, does not delete).\n\nUser will be unable to log in. Their peers remain in the VPN DB\nbut they cannot authenticate to manage them.","operationId":"revoke_user_api_users__username__revoke_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"username","in":"path","required":true,"schema":{"type":"string","title":"Username"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges":{"get":{"tags":["CCM Edge"],"summary":"List Edges","operationId":"list_edges_api_ccm_edge_edges_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"state","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["CCM Edge"],"summary":"Create Edge","operationId":"create_edge_api_ccm_edge_edges_post","security":[{"APIKeyHeader":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EdgeCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}":{"get":{"tags":["CCM Edge"],"summary":"Get Edge","operationId":"get_edge_api_ccm_edge_edges__edge_id__get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"tags":["CCM Edge"],"summary":"Update Edge","operationId":"update_edge_api_ccm_edge_edges__edge_id__patch","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EdgeUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["CCM Edge"],"summary":"Delete Edge","operationId":"delete_edge_api_ccm_edge_edges__edge_id__delete","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/activate":{"post":{"tags":["CCM Edge"],"summary":"Activate Edge","description":"Validate edge and sub-resources. Test SSH. Transition draft/inactive → active.","operationId":"activate_edge_api_ccm_edge_edges__edge_id__activate_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/deactivate":{"post":{"tags":["CCM Edge"],"summary":"Deactivate Edge","description":"Transition edge active/planned → inactive. Idempotent if already inactive.","operationId":"deactivate_edge_api_ccm_edge_edges__edge_id__deactivate_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/deploy":{"post":{"tags":["CCM Edge"],"summary":"Deploy Edge","description":"Deploy an edge's CCM-managed artifacts.\n\nArgs:\n    dry_run: If True, render but do not write or apply. Returns the render.\n    detached: If True and the deploy includes interface-realization changes\n        (per I-INTERFACE-REALIZATION-1), schedule ifreload via\n        `systemd-run --no-block` instead of synchronous SSH. Use when the\n        change might touch the deploy SSH path. See W2.2:\n        https://docs.charliehub.net/control-plane/ccm-interface-realization-design/\n\nTODO(W4): auto-detach fallback when wg-site handshake is degraded; W2.4\n    error envelope shape (today: RuntimeError → 500; W2.4 specifies\n    200 with classified drift entries).","operationId":"deploy_edge_api_ccm_edge_edges__edge_id__deploy_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"dry_run","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Dry Run"}},{"name":"detached","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Detached"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/pre-render":{"post":{"tags":["CCM Edge"],"summary":"Pre Render Edge","description":"Generate hook content from DB and persist to ccm.artifacts.content.\n\nNo SSH. Pure function(DB). Must be called before transit deploy so that\nTM render can embed ccm_hooks in the node payload (reads DB only).","operationId":"pre_render_edge_api_ccm_edge_edges__edge_id__pre_render_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/artifacts":{"get":{"tags":["CCM Edge"],"summary":"Get Artifacts","operationId":"get_artifacts_api_ccm_edge_edges__edge_id__artifacts_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["CCM Edge"],"summary":"Create Artifact","operationId":"create_artifact_api_ccm_edge_edges__edge_id__artifacts_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArtifactCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/artifacts/consistency":{"get":{"tags":["CCM Edge"],"summary":"Artifact Consistency","description":"Compare DB content (ccm.artifacts.content) against a fresh render.\n\nReturns per-artifact status: ok | drift_db_render | missing | skipped.\nAlso updates ccm_artifact_drift_total Prometheus gauge (0=clean, 1=drift).","operationId":"artifact_consistency_api_ccm_edge_edges__edge_id__artifacts_consistency_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/consistency":{"get":{"tags":["CCM Edge"],"summary":"Edge Consistency","description":"Full cross-system consistency check for a CCM edge.\n\nChecks:\n  - tm_node_state: CCM edge active → TM node must exist and be active\n  - tm_node_exists: node_hostname must resolve to a known TM node (even if inactive)\n  - artifact drift: delegates to /artifacts/consistency logic\n\nReturns overall ok=True only when all checks pass.","operationId":"edge_consistency_api_ccm_edge_edges__edge_id__consistency_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/artifacts/{artifact_id}":{"patch":{"tags":["CCM Edge"],"summary":"Update Artifact","operationId":"update_artifact_api_ccm_edge_edges__edge_id__artifacts__artifact_id__patch","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"artifact_id","in":"path","required":true,"schema":{"type":"integer","title":"Artifact Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ArtifactUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["CCM Edge"],"summary":"Delete Artifact","operationId":"delete_artifact_api_ccm_edge_edges__edge_id__artifacts__artifact_id__delete","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"artifact_id","in":"path","required":true,"schema":{"type":"integer","title":"Artifact Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/interfaces":{"get":{"tags":["CCM Edge"],"summary":"Get Interfaces","operationId":"get_interfaces_api_ccm_edge_edges__edge_id__interfaces_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["CCM Edge"],"summary":"Create Interface","operationId":"create_interface_api_ccm_edge_edges__edge_id__interfaces_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterfaceCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/interfaces/{iface_id}":{"patch":{"tags":["CCM Edge"],"summary":"Update Interface","operationId":"update_interface_api_ccm_edge_edges__edge_id__interfaces__iface_id__patch","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"iface_id","in":"path","required":true,"schema":{"type":"integer","title":"Iface Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterfaceUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["CCM Edge"],"summary":"Delete Interface","operationId":"delete_interface_api_ccm_edge_edges__edge_id__interfaces__iface_id__delete","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"iface_id","in":"path","required":true,"schema":{"type":"integer","title":"Iface Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/routing":{"get":{"tags":["CCM Edge"],"summary":"Get Routing","operationId":"get_routing_api_ccm_edge_edges__edge_id__routing_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/routing/tables":{"post":{"tags":["CCM Edge"],"summary":"Create Routing Table","operationId":"create_routing_table_api_ccm_edge_edges__edge_id__routing_tables_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoutingTableCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/routing/tables/{table_id}":{"patch":{"tags":["CCM Edge"],"summary":"Update Routing Table","operationId":"update_routing_table_api_ccm_edge_edges__edge_id__routing_tables__table_id__patch","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"table_id","in":"path","required":true,"schema":{"type":"integer","title":"Table Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RoutingTableUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["CCM Edge"],"summary":"Delete Routing Table","operationId":"delete_routing_table_api_ccm_edge_edges__edge_id__routing_tables__table_id__delete","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"table_id","in":"path","required":true,"schema":{"type":"integer","title":"Table Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/routing/rules":{"post":{"tags":["CCM Edge"],"summary":"Create Policy Rule","operationId":"create_policy_rule_api_ccm_edge_edges__edge_id__routing_rules_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicyRuleCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/routing/rules/{rule_id}":{"patch":{"tags":["CCM Edge"],"summary":"Update Policy Rule","operationId":"update_policy_rule_api_ccm_edge_edges__edge_id__routing_rules__rule_id__patch","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"rule_id","in":"path","required":true,"schema":{"type":"integer","title":"Rule Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PolicyRuleUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["CCM Edge"],"summary":"Delete Policy Rule","operationId":"delete_policy_rule_api_ccm_edge_edges__edge_id__routing_rules__rule_id__delete","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"rule_id","in":"path","required":true,"schema":{"type":"integer","title":"Rule Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/nat":{"get":{"tags":["CCM Edge"],"summary":"Get Nat","operationId":"get_nat_api_ccm_edge_edges__edge_id__nat_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["CCM Edge"],"summary":"Create Nat Rule","operationId":"create_nat_rule_api_ccm_edge_edges__edge_id__nat_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NatRuleCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/nat/{rule_id}":{"patch":{"tags":["CCM Edge"],"summary":"Update Nat Rule","operationId":"update_nat_rule_api_ccm_edge_edges__edge_id__nat__rule_id__patch","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"rule_id","in":"path","required":true,"schema":{"type":"integer","title":"Rule Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/NatRuleUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["CCM Edge"],"summary":"Delete Nat Rule","operationId":"delete_nat_rule_api_ccm_edge_edges__edge_id__nat__rule_id__delete","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"rule_id","in":"path","required":true,"schema":{"type":"integer","title":"Rule Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/services":{"get":{"tags":["CCM Edge"],"summary":"Get Services","operationId":"get_services_api_ccm_edge_edges__edge_id__services_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"post":{"tags":["CCM Edge"],"summary":"Create Edge Service","operationId":"create_edge_service_api_ccm_edge_edges__edge_id__services_post","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EdgeServiceCreate"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/services/{service_id}":{"patch":{"tags":["CCM Edge"],"summary":"Update Edge Service","operationId":"update_edge_service_api_ccm_edge_edges__edge_id__services__service_id__patch","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"service_id","in":"path","required":true,"schema":{"type":"integer","title":"Service Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EdgeServiceUpdate"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"tags":["CCM Edge"],"summary":"Delete Edge Service","operationId":"delete_edge_service_api_ccm_edge_edges__edge_id__services__service_id__delete","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"service_id","in":"path","required":true,"schema":{"type":"integer","title":"Service Id"}}],"responses":{"204":{"description":"Successful Response"},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/snapshots":{"get":{"tags":["CCM Edge"],"summary":"Get Snapshots","operationId":"get_snapshots_api_ccm_edge_edges__edge_id__snapshots_get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"minimum":1,"default":10,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/edge/edges/{edge_id}/snapshots/{snapshot_id}":{"get":{"tags":["CCM Edge"],"summary":"Get Snapshot Detail","operationId":"get_snapshot_detail_api_ccm_edge_edges__edge_id__snapshots__snapshot_id__get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_id","in":"path","required":true,"schema":{"type":"integer","title":"Edge Id"}},{"name":"snapshot_id","in":"path","required":true,"schema":{"type":"integer","title":"Snapshot Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ccm/security/render/{edge_name}":{"get":{"tags":["CCM Security"],"summary":"Preview the rendered charliehub.conf for an edge","description":"Return the rendered charliehub.conf body + diagnostic envelope.\n\nHard-fail (HTTP 409) on: unknown / inactive edge, missing security_profile,\nempty trust_boundaries, empty operator_source_set.\n\nAuth: requires X-API-Key.","operationId":"render_security_api_ccm_security_render__edge_name__get","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"edge_name","in":"path","required":true,"schema":{"type":"string","title":"Edge Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/health":{"get":{"summary":"Health Legacy","description":"Health check endpoint - no authentication required (legacy path, prefer /api/health)","operationId":"health_legacy_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HealthResponse"}}}}}}},"/api/ssh-targets":{"get":{"summary":"List SSH targets","description":"Get SSH targets for infrastructure page (public endpoint)","operationId":"list_ssh_targets_api_ssh_targets_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/ssh-targets/{target_id}/shortcut":{"put":{"summary":"Update SSH target shortcut","description":"Update the shortcut (alias) for an SSH target","operationId":"update_ssh_shortcut_api_ssh_targets__target_id__shortcut_put","parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"integer","title":"Target Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"Data"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ssh-targets/{target_id}/ssh-user":{"put":{"summary":"Update SSH target user","description":"Update the SSH user for an SSH target","operationId":"update_ssh_user_api_ssh_targets__target_id__ssh_user_put","parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"integer","title":"Target Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"Data"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ssh-targets/{target_id}/description":{"put":{"summary":"Update SSH target description","description":"Update the description/name for an SSH target","operationId":"update_ssh_description_api_ssh_targets__target_id__description_put","parameters":[{"name":"target_id","in":"path","required":true,"schema":{"type":"integer","title":"Target Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","title":"Data"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ssh-targets/sync-names":{"post":{"summary":"Sync names from Proxmox","description":"Pull VM/CT names from Proxmox and update descriptions","operationId":"sync_names_from_proxmox_api_ssh_targets_sync_names_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/vmid/calculate-ip":{"get":{"summary":"Calculate IP from VMID","description":"Calculate what IP address a VMID will get based on convention","operationId":"api_vmid_calculate_ip_api_vmid_calculate_ip_get","parameters":[{"name":"vmid","in":"query","required":true,"schema":{"type":"integer","title":"Vmid"}},{"name":"asset_type","in":"query","required":true,"schema":{"type":"string","title":"Asset Type"}},{"name":"node","in":"query","required":false,"schema":{"type":"string","default":"px1-silverstone","title":"Node"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/vmid/suggest":{"get":{"summary":"Suggest VMID for IP","description":"Suggest a VMID that will produce the desired IP address","operationId":"api_vmid_suggest_api_vmid_suggest_get","parameters":[{"name":"desired_ip","in":"query","required":true,"schema":{"type":"string","title":"Desired Ip"}},{"name":"asset_type","in":"query","required":true,"schema":{"type":"string","title":"Asset Type"}},{"name":"node","in":"query","required":false,"schema":{"type":"string","default":"px1-silverstone","title":"Node"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/vmid/validate":{"get":{"summary":"Validate VMID/IP combination","description":"Check if a VMID/IP combination follows the convention","operationId":"api_vmid_validate_api_vmid_validate_get","parameters":[{"name":"vmid","in":"query","required":true,"schema":{"type":"integer","title":"Vmid"}},{"name":"ip_address","in":"query","required":true,"schema":{"type":"string","title":"Ip Address"}},{"name":"asset_type","in":"query","required":true,"schema":{"type":"string","title":"Asset Type"}},{"name":"node","in":"query","required":false,"schema":{"type":"string","default":"px1-silverstone","title":"Node"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/vmid/available":{"get":{"summary":"List available VMIDs","description":"List available VMIDs with their corresponding IPs","operationId":"api_vmid_available_api_vmid_available_get","parameters":[{"name":"asset_type","in":"query","required":true,"schema":{"type":"string","title":"Asset Type"}},{"name":"node","in":"query","required":false,"schema":{"type":"string","default":"px1-silverstone","title":"Node"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","default":10,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/deploy/render":{"post":{"summary":"Render Traefik config","description":"Render Traefik config from DB. No filesystem writes. Returns YAML + metadata.","operationId":"deploy_render_api_deploy_render_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/system/status":{"get":{"summary":"System status","description":"Get overall system status including domain counts and zones","operationId":"system_status_api_system_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response System Status Api System Status Get"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/domain/integrity":{"get":{"summary":"Domain integrity","description":"Compare render_domains() against live routes.yml. Mismatch = drift.","operationId":"domain_integrity_api_domain_integrity_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"type":"object","title":"Response Domain Integrity Api Domain Integrity Get"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/docker/discover":{"get":{"summary":"Discover containers","description":"Discover all Docker containers on current host","operationId":"docker_discover_api_docker_discover_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/infrastructure/status":{"get":{"summary":"Infrastructure status","description":"Get current infrastructure status including Docker and domain counts","operationId":"infrastructure_status_api_infrastructure_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/proxmox/vms":{"get":{"summary":"List Proxmox VMs and CTs","description":"Get all VMs and CTs from Proxmox cluster, grouped by node.","operationId":"get_proxmox_vms_api_proxmox_vms_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/proxmox/vms/{vmid}/start":{"post":{"summary":"Start a VM or CT","description":"Start a stopped VM or CT on the Proxmox cluster.","operationId":"start_vm_api_proxmox_vms__vmid__start_post","parameters":[{"name":"vmid","in":"path","required":true,"schema":{"type":"integer","title":"Vmid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/proxmox/vms/{vmid}/stop":{"post":{"summary":"Stop a VM or CT","description":"Gracefully stop a running VM or CT on the Proxmox cluster.","operationId":"stop_vm_api_proxmox_vms__vmid__stop_post","parameters":[{"name":"vmid","in":"path","required":true,"schema":{"type":"integer","title":"Vmid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/vmid/next":{"get":{"summary":"Get next available VMID using [N][P][SS] convention","description":"Get next available VMID using [N][P][SS] convention.","operationId":"api_next_vmid_v2_api_vmid_next_get","parameters":[{"name":"node","in":"query","required":true,"schema":{"type":"string","title":"Node"}},{"name":"purpose","in":"query","required":true,"schema":{"type":"string","title":"Purpose"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/vmid/decode":{"get":{"summary":"Decode VMID to understand its node and purpose","description":"Decode a VMID to understand its convention.","operationId":"api_vmid_decode_v2_api_vmid_decode_get","parameters":[{"name":"vmid","in":"query","required":true,"schema":{"type":"integer","title":"Vmid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ip/next":{"get":{"summary":"Get next available static IP","description":"Get next available static IP.","operationId":"api_next_ip_v2_api_ip_next_get","parameters":[{"name":"site","in":"query","required":false,"schema":{"type":"string","default":"uk","title":"Site"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ip/used":{"get":{"summary":"List used IP addresses","description":"List used IP addresses from infrastructure_assets.","operationId":"api_used_ips_v2_api_ip_used_get","parameters":[{"name":"site","in":"query","required":false,"schema":{"type":"string","title":"Site"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/assets":{"get":{"summary":"List infrastructure assets","description":"List infrastructure assets with optional filters.","operationId":"list_assets_v2_api_assets_get","parameters":[{"name":"node","in":"query","required":false,"schema":{"type":"string","title":"Node"}},{"name":"status","in":"query","required":false,"schema":{"type":"string","title":"Status"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/assets/{vmid}":{"get":{"summary":"Get infrastructure asset by VMID","description":"Get single asset by VMID.","operationId":"get_asset_v2_api_assets__vmid__get","parameters":[{"name":"vmid","in":"path","required":true,"schema":{"type":"integer","title":"Vmid"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"patch":{"summary":"Update infrastructure asset identity (name, dns_aliases)","description":"Sparse PATCH. Gated by ENABLE_ASSET_PATCH. Full contract at https://docs.charliehub.net/control-plane/specs/patch-assets/","operationId":"patch_asset_v2_api_assets__vmid__patch","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"vmid","in":"path","required":true,"schema":{"type":"integer","title":"Vmid"}},{"name":"If-Match","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"If-Match"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AssetPatchRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/reconciliation":{"get":{"summary":"Infrastructure reconciliation status","description":"Reconciliation state for all infrastructure assets: synced | drifted | orphaned | stale | unknown. Populated by lan-watcher on each Proxmox sync cycle (default: 10 min).","operationId":"infrastructure_reconciliation_api_reconciliation_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/infrastructure/dns":{"get":{"summary":"Infrastructure DNS records (dry-run view)","description":"Returns the (fqdn, ip) records the dnsmasq generator emits for the infrastructure-identity section, sourced from infrastructure_assets (canonical hostnames + dns_aliases). Read-only — the actual write happens via deploy_dnsmasq_config() during /api/deploy-all.","operationId":"get_infrastructure_dns_api_infrastructure_dns_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/cluster/status":{"get":{"summary":"Get cluster health status","description":"Get aggregated cluster health status from Prometheus, Alertmanager, and Proxmox.\nReturns node status, Ceph health, active alerts, and service status.","operationId":"get_cluster_status_api_cluster_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/nodes":{"get":{"summary":"List all nodes","description":"Get all infrastructure nodes from database.","operationId":"list_nodes_api_nodes_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}},"post":{"summary":"Create a new node","description":"Create a new infrastructure node.","operationId":"create_node_api_nodes_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_create_node_api_nodes_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"security":[{"APIKeyHeader":[]}]}},"/api/nodes/{name}":{"get":{"summary":"Get node by name","description":"Get a specific node by name.","operationId":"get_node_api_nodes__name__get","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"put":{"summary":"Update a node","description":"Update an existing node.","operationId":"update_node_api_nodes__name__put","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_update_node_api_nodes__name__put"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"Delete a node","description":"Delete a node by name.","operationId":"delete_node_api_nodes__name__delete","security":[{"APIKeyHeader":[]}],"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string","title":"Name"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/ssh-config":{"get":{"summary":"Generate SSH config","description":"Generate ~/.ssh/config entries for all infrastructure nodes.\n\nUsage:\n  curl https://charliehub.net/api/ssh-config >> ~/.ssh/config\n\nOr to view first:\n  curl https://charliehub.net/api/ssh-config","operationId":"generate_ssh_config_api_ssh_config_get","parameters":[{"name":"include_clients","in":"query","required":false,"schema":{"type":"boolean","default":false,"title":"Include Clients"}}],"responses":{"200":{"description":"Successful Response","content":{"text/plain":{"schema":{"type":"string"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/sync/preview":{"get":{"summary":"Preview sync changes","description":"Preview what would change if we sync database with Proxmox (dry run).","operationId":"sync_preview_v2_api_sync_preview_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/conflicts":{"get":{"summary":"Detect IP and VMID conflicts","description":"Detect IP conflicts and orphaned VMIDs.","operationId":"detect_conflicts_v2_api_conflicts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/vmid/wizard-suggest":{"get":{"summary":"VMID Wizard Suggestion","description":"Get comprehensive VMID/IP suggestion for the planning wizard","operationId":"api_vmid_wizard_suggest_api_vmid_wizard_suggest_get","parameters":[{"name":"node","in":"query","required":true,"schema":{"type":"string","title":"Node"}},{"name":"purpose","in":"query","required":true,"schema":{"type":"string","title":"Purpose"}},{"name":"asset_type","in":"query","required":false,"schema":{"type":"string","default":"ct","title":"Asset Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/vmid/check":{"get":{"summary":"Check Custom VMID Availability","description":"Check if a custom VMID is valid and available","operationId":"api_vmid_check_api_vmid_check_get","parameters":[{"name":"vmid","in":"query","required":true,"schema":{"type":"integer","title":"Vmid"}},{"name":"asset_type","in":"query","required":false,"schema":{"type":"string","default":"ct","title":"Asset Type"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/vmid/validate-ip":{"get":{"summary":"Validate IP Address","description":"Check if an IP address is available","operationId":"api_vmid_validate_ip_api_vmid_validate_ip_get","parameters":[{"name":"ip","in":"query","required":true,"schema":{"type":"string","title":"Ip"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/api/orphans":{"get":{"summary":"List orphaned database entries","description":"List VMIDs in database that no longer exist in Proxmox.","operationId":"api_list_orphans_api_orphans_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}}}},"/api/orphans/cleanup":{"post":{"summary":"Clean up orphaned entries","description":"Remove orphaned entries from database. If vmids not provided, removes all orphans.","operationId":"api_cleanup_orphans_api_orphans_cleanup_post","requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Request"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"AliasInfo":{"properties":{"email":{"type":"string","title":"Email"},"display_name":{"type":"string","title":"Display Name"},"is_primary":{"type":"boolean","title":"Is Primary"},"is_verified":{"type":"boolean","title":"Is Verified"}},"type":"object","required":["email","display_name","is_primary","is_verified"],"title":"AliasInfo"},"AliasesResponse":{"properties":{"aliases":{"items":{"$ref":"#/components/schemas/AliasInfo"},"type":"array","title":"Aliases"}},"type":"object","required":["aliases"],"title":"AliasesResponse"},"ArtifactCreate":{"properties":{"path":{"type":"string","title":"Path"},"owner":{"type":"string","title":"Owner"},"managed":{"type":"boolean","title":"Managed","default":false},"template":{"type":"string","title":"Template","default":"manual"},"state":{"type":"string","title":"State","default":"active"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["path","owner"],"title":"ArtifactCreate"},"ArtifactUpdate":{"properties":{"owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner"},"managed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Managed"},"template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"last_revision":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Last Revision"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"ArtifactUpdate"},"AssetPatchRequest":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"New canonical hostname (RFC-1123 label)."},"dns_aliases":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Dns Aliases","description":"FULL REPLACEMENT of the alias set. Not a merge. Caller owns backward-compat."}},"type":"object","title":"AssetPatchRequest"},"AutodetectRequest":{"properties":{"probe_path":{"type":"string","title":"Probe Path","default":"/"},"timeout":{"type":"integer","title":"Timeout","default":3}},"type":"object","title":"AutodetectRequest","description":"Request model for auto-detecting service type"},"Body_create_node_api_nodes_post":{"properties":{"name":{"type":"string","title":"Name"},"display_name":{"type":"string","title":"Display Name"},"tailscale_ip":{"type":"string","title":"Tailscale Ip"},"lan_ip":{"type":"string","title":"Lan Ip"},"location":{"type":"string","title":"Location"},"node_type":{"type":"string","title":"Node Type","default":"client"},"ssh_user":{"type":"string","title":"Ssh User","default":"root"},"description":{"type":"string","title":"Description"},"is_tailscale_node":{"type":"boolean","title":"Is Tailscale Node","default":false},"routes":{"type":"string","title":"Routes"}},"type":"object","required":["name"],"title":"Body_create_node_api_nodes_post"},"Body_update_node_api_nodes__name__put":{"properties":{"display_name":{"type":"string","title":"Display Name"},"tailscale_ip":{"type":"string","title":"Tailscale Ip"},"lan_ip":{"type":"string","title":"Lan Ip"},"location":{"type":"string","title":"Location"},"node_type":{"type":"string","title":"Node Type"},"ssh_user":{"type":"string","title":"Ssh User"},"description":{"type":"string","title":"Description"},"is_tailscale_node":{"type":"boolean","title":"Is Tailscale Node"},"routes":{"type":"string","title":"Routes"}},"type":"object","title":"Body_update_node_api_nodes__name__put"},"DNSZonesResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"zones":{"items":{"type":"string"},"type":"array","title":"Zones"},"warning":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Warning"}},"type":"object","required":["success","zones"],"title":"DNSZonesResponse","description":"DNS zones response"},"DeploymentResult":{"properties":{"success":{"type":"boolean","title":"Success"},"message":{"type":"string","title":"Message"},"traefik":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Traefik"},"ddns":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Ddns"},"dns":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Dns"},"tls":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Tls"}},"type":"object","required":["success","message"],"title":"DeploymentResult","description":"Result of a deployment operation"},"DomainCreate":{"properties":{"domain":{"type":"string","maxLength":255,"minLength":3,"title":"Domain","description":"Fully qualified domain name"},"protocol":{"type":"string","enum":["http","tcp","udp"],"title":"Protocol","description":"Transport protocol (http, tcp, or udp for external endpoints)","default":"http"},"service_type":{"type":"string","enum":["api","spa","proxy","static","websocket","tcp-proxy","internal","http","app","manual","external-endpoint"],"title":"Service Type","description":"Type of service (manual = externally managed, not auto-generated)"},"tcp_entrypoint":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Tcp Entrypoint","description":"TCP entrypoint name (e.g., mqtt, ssh, postgres) - required for tcp protocol"},"environment":{"type":"string","enum":["production","development"],"title":"Environment","description":"Environment","default":"production"},"backend_host":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Backend Host","description":"Backend server IP or hostname"},"backend_port":{"anyOf":[{"type":"integer","maximum":65535.0,"minimum":1.0},{"type":"null"}],"title":"Backend Port","description":"Backend server port"},"cors_enabled":{"type":"boolean","title":"Cors Enabled","description":"Enable CORS middleware","default":false},"cors_origin":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Cors Origin","description":"CORS allowed origin"},"default_path":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Default Path","description":"Default redirect path (e.g., /app)"},"ssh_user":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Ssh User","description":"SSH username for bastion routing"},"auth_required":{"type":"boolean","title":"Auth Required","description":"Require Authelia authentication","default":true},"authelia_policy":{"anyOf":[{"type":"string","enum":["bypass","one_factor","two_factor"]},{"type":"null"}],"title":"Authelia Policy","description":"Authelia access-control policy for this domain. REQUIRED when auth_required=true; MUST be NULL otherwise. Mirrored to access_control.rules in configuration.yml. Falls through to access_control.default_policy when NULL."},"authelia_subject":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Authelia Subject","description":"Optional Authelia subject filter (e.g. 'group:vpn-admin', 'user:guest'). When NULL, the rule applies to all authenticated users."},"access_scope":{"type":"string","enum":["public","fabric","lan"],"title":"Access Scope","description":"Network access tier: public (internet), fabric (SD-WAN + homelab), lan (direct LAN only)","default":"public"},"cert_resolver":{"type":"string","enum":["letsencrypt","letsencrypt-dns"],"title":"Cert Resolver","description":"ACME resolver: letsencrypt=TLS-ALPN-01 (default), letsencrypt-dns=DNS-01 via Cloudflare (required when CF-proxied)","default":"letsencrypt"},"backend_resolution":{"type":"string","enum":["auto","localhost","docker_ip","static_ip"],"title":"Backend Resolution","description":"How Traefik resolves this backend: auto (infer), localhost (127.0.0.1 port binding), docker_ip (bridge IP), static_ip (use as-is)","default":"auto"},"backend_scheme":{"type":"string","enum":["http","https"],"title":"Backend Scheme","description":"Scheme used when Traefik connects to the backend (http or https)","default":"http"},"backend_tls_verify":{"type":"string","enum":["strict","skip"],"title":"Backend Tls Verify","description":"TLS verification for HTTPS backends: strict (verify cert) or skip (insecureSkipVerify)","default":"strict"},"backend_sni":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Backend Sni","description":"Optional SNI hostname override for HTTPS backends that require a hostname distinct from backend_host"},"wan_watcher_enabled":{"anyOf":[{"type":"string","enum":["uk","fr","us","pl"]},{"type":"null"}],"title":"Wan Watcher Enabled","description":"Enable WAN-Watcher DDNS updates (uk or fr) - auto-updates DNS when WAN IP changes"},"external_ip":{"anyOf":[{"type":"string","maxLength":45},{"type":"null"}],"title":"External Ip","description":"Canonical public WAN IP for external (UDP) endpoints. Domain Manager is the source of truth for this endpoint's identity."},"external_port":{"anyOf":[{"type":"integer","maximum":65535.0,"minimum":1.0},{"type":"null"}],"title":"External Port","description":"UDP port for external endpoints (e.g. 51821 for WireGuard)"},"service_key":{"anyOf":[{"type":"string","maxLength":100,"pattern":"^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$"},{"type":"null"}],"title":"Service Key","description":"Groups multiple domains to one backend service. All domains in a group must share identical backend config."},"localhost_service_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Localhost Service Id","description":"Registry entry ID from localhost_services table. Required when backend_resolution=localhost (except domain 54 TCP exception)."}},"type":"object","required":["domain","service_type"],"title":"DomainCreate","description":"Model for creating a new domain"},"DomainIdentityResponse":{"properties":{"id":{"type":"integer","title":"Id"},"domain":{"type":"string","title":"Domain"},"protocol":{"type":"string","title":"Protocol"},"service_type":{"type":"string","title":"Service Type"},"status":{"type":"string","title":"Status"},"external_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Ip"},"external_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"External Port"}},"type":"object","required":["id","domain","protocol","service_type","status"],"title":"DomainIdentityResponse","description":"Narrow response model for monitoring agents (e.g. wg-monitor).\n\nContains only the identity fields needed to verify endpoint state:\n- what the domain is (FQDN, protocol, service_type)\n- where it lives (external_ip, external_port)\n- whether it's active (status)\n\nDeliberately excludes infrastructure topology (backend_host, vmid,\ncontainer_name, failover_ip) that monitoring agents do not need."},"DomainResponse":{"properties":{"domain":{"type":"string","maxLength":255,"minLength":3,"title":"Domain","description":"Fully qualified domain name"},"protocol":{"type":"string","title":"Protocol","default":"http"},"service_type":{"type":"string","enum":["api","spa","proxy","static","websocket","tcp-proxy","internal","http","app","manual","external-endpoint"],"title":"Service Type","description":"Type of service (manual = externally managed, not auto-generated)"},"tcp_entrypoint":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tcp Entrypoint"},"environment":{"type":"string","enum":["production","development"],"title":"Environment","description":"Environment","default":"production"},"backend_host":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Backend Host","description":"Backend server IP or hostname"},"backend_port":{"anyOf":[{"type":"integer","maximum":65535.0,"minimum":1.0},{"type":"null"}],"title":"Backend Port","description":"Backend server port"},"cors_enabled":{"type":"boolean","title":"Cors Enabled","description":"Enable CORS middleware","default":false},"cors_origin":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Cors Origin","description":"CORS allowed origin"},"default_path":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Default Path","description":"Default redirect path (e.g., /app)"},"ssh_user":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Ssh User","description":"SSH username for bastion routing"},"auth_required":{"type":"boolean","title":"Auth Required","default":true},"authelia_policy":{"anyOf":[{"type":"string","enum":["bypass","one_factor","two_factor"]},{"type":"null"}],"title":"Authelia Policy","description":"Authelia access-control policy for this domain. REQUIRED when auth_required=true; MUST be NULL otherwise. Mirrored to access_control.rules in configuration.yml. Falls through to access_control.default_policy when NULL."},"authelia_subject":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Authelia Subject","description":"Optional Authelia subject filter (e.g. 'group:vpn-admin', 'user:guest'). When NULL, the rule applies to all authenticated users."},"access_scope":{"type":"string","enum":["public","fabric","lan"],"title":"Access Scope","description":"Network access tier: public (internet), fabric (SD-WAN + homelab), lan (direct LAN only)","default":"public"},"cert_resolver":{"type":"string","title":"Cert Resolver","default":"letsencrypt"},"backend_resolution":{"type":"string","enum":["auto","localhost","docker_ip","static_ip"],"title":"Backend Resolution","description":"How Traefik resolves this backend: auto (infer), localhost (127.0.0.1 port binding), docker_ip (bridge IP), static_ip (use as-is)","default":"auto"},"backend_scheme":{"type":"string","title":"Backend Scheme","default":"http"},"backend_tls_verify":{"type":"string","title":"Backend Tls Verify","default":"strict"},"backend_sni":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Backend Sni"},"wan_watcher_enabled":{"anyOf":[{"type":"string","enum":["uk","fr","us","pl"]},{"type":"null"}],"title":"Wan Watcher Enabled"},"external_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"External Ip"},"external_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"External Port"},"service_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service Key"},"localhost_service_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Localhost Service Id"},"id":{"type":"integer","title":"Id"},"revision":{"type":"integer","title":"Revision","description":"Optimistic locking revision number","default":1},"ssl_enabled":{"type":"boolean","title":"Ssl Enabled","default":true},"status":{"type":"string","title":"Status","default":"active"},"created_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Created At"},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At"},"ddns_enabled":{"type":"boolean","title":"Ddns Enabled","default":true},"internal_only":{"type":"boolean","title":"Internal Only","default":false},"ddns_zone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ddns Zone"},"ddns_subdomain":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ddns Subdomain"},"ddns_record_type":{"type":"string","title":"Ddns Record Type","default":"A"},"ddns_ttl":{"type":"integer","title":"Ddns Ttl","default":300},"ddns_last_update":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Ddns Last Update"},"ddns_status":{"type":"string","title":"Ddns Status","default":"pending"},"ddns_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ddns Error"},"static_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Static Ip"},"static_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Static Port"},"container_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Container Name"},"vmid":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Vmid"},"failover_enabled":{"type":"boolean","title":"Failover Enabled","default":false},"failover_ip":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failover Ip"},"failover_port":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Failover Port"},"failover_site":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Failover Site"},"middleware_config":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Middleware Config"},"health_check_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Health Check Path"},"priority":{"type":"integer","title":"Priority","default":100},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"last_health_check":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Health Check"},"health_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Health Status"},"tls_mode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Tls Mode"},"cert_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Cert Status"},"cert_issued_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cert Issued At"},"cert_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Cert Expires At"},"warnings":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Warnings"}},"type":"object","required":["domain","service_type","id"],"title":"DomainResponse","description":"Model for domain response with database fields"},"DomainUpdate":{"properties":{"backend_host":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Backend Host"},"backend_port":{"anyOf":[{"type":"integer","maximum":65535.0,"minimum":1.0},{"type":"null"}],"title":"Backend Port"},"cors_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Cors Enabled"},"cors_origin":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Cors Origin"},"auth_required":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Auth Required"},"authelia_policy":{"anyOf":[{"type":"string","enum":["bypass","one_factor","two_factor"]},{"type":"null"}],"title":"Authelia Policy"},"authelia_subject":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Authelia Subject"},"wan_watcher_enabled":{"anyOf":[{"type":"string","enum":["uk","fr","us","pl"]},{"type":"null"}],"title":"Wan Watcher Enabled"},"default_path":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Default Path"},"health_check_path":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Health Check Path"},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"},"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority"},"middleware_config":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Middleware Config"},"cert_resolver":{"anyOf":[{"type":"string","enum":["letsencrypt","letsencrypt-dns"]},{"type":"null"}],"title":"Cert Resolver"},"protocol":{"anyOf":[{"type":"string","enum":["http","tcp","udp"]},{"type":"null"}],"title":"Protocol"},"tcp_entrypoint":{"anyOf":[{"type":"string","maxLength":50},{"type":"null"}],"title":"Tcp Entrypoint"},"service_type":{"anyOf":[{"type":"string","enum":["api","spa","proxy","static","websocket","tcp-proxy","internal","http","app","manual","external-endpoint"]},{"type":"null"}],"title":"Service Type"},"external_ip":{"anyOf":[{"type":"string","maxLength":45},{"type":"null"}],"title":"External Ip"},"external_port":{"anyOf":[{"type":"integer","maximum":65535.0,"minimum":1.0},{"type":"null"}],"title":"External Port"},"ddns_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Ddns Enabled"},"internal_only":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Internal Only"},"access_scope":{"anyOf":[{"type":"string","enum":["public","fabric","lan"]},{"type":"null"}],"title":"Access Scope"},"backend_resolution":{"anyOf":[{"type":"string","enum":["auto","localhost","docker_ip","static_ip"]},{"type":"null"}],"title":"Backend Resolution"},"backend_scheme":{"anyOf":[{"type":"string","enum":["http","https"]},{"type":"null"}],"title":"Backend Scheme"},"backend_tls_verify":{"anyOf":[{"type":"string","enum":["strict","skip"]},{"type":"null"}],"title":"Backend Tls Verify"},"backend_sni":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Backend Sni"},"service_key":{"anyOf":[{"type":"string","maxLength":100,"pattern":"^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$"},{"type":"null"}],"title":"Service Key"},"localhost_service_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Localhost Service Id"},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status"},"ddns_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ddns Status"},"dns_managed":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Dns Managed"},"ddns_error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ddns Error"}},"type":"object","title":"DomainUpdate","description":"Domain update schema – LIFECYCLE FIELDS IMMUTABLE.\n\nThis schema rejects any attempt to mutate lifecycle fields.\nUse activate_domain() or disable_domain() endpoints instead."},"EdgeCreate":{"properties":{"name":{"type":"string","title":"Name"},"role":{"type":"string","title":"Role"},"ssh_host":{"type":"string","title":"Ssh Host"},"ssh_user":{"type":"string","title":"Ssh User","default":"root"},"site_prefix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Prefix"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["name","role","ssh_host"],"title":"EdgeCreate"},"EdgeServiceCreate":{"properties":{"service_name":{"type":"string","title":"Service Name"},"service_type":{"type":"string","title":"Service Type"},"config_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Config Path"},"config_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Config Key"},"config_value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Config Value"},"template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template"},"owner":{"type":"string","title":"Owner","default":"ccm_edge"},"state":{"type":"string","title":"State","default":"draft"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["service_name","service_type"],"title":"EdgeServiceCreate"},"EdgeServiceUpdate":{"properties":{"service_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Service Type"},"config_path":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Config Path"},"config_key":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Config Key"},"config_value":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Config Value"},"template":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Template"},"owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"EdgeServiceUpdate"},"EdgeUpdate":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name"},"role":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Role"},"ssh_host":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ssh Host"},"ssh_user":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ssh User"},"site_prefix":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Site Prefix"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"},"mss_clamp_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Mss Clamp Enabled"},"wan_interface":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Wan Interface"},"node_hostname":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Node Hostname"},"mtu_enforcement":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Mtu Enforcement"},"wan_mtu":{"anyOf":[{"type":"integer","maximum":9000.0,"minimum":576.0},{"type":"null"}],"title":"Wan Mtu","description":"Kernel MTU to enforce on wan_interface when mtu_enforcement=true. Bounded [576, 9000] (IPv4 minimum to jumbo upper)."},"wan_runtime_enabled":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Wan Runtime Enabled"}},"additionalProperties":false,"type":"object","title":"EdgeUpdate"},"EmailRequest":{"properties":{"from_address":{"type":"string","format":"email","title":"From Address"},"to":{"type":"string","format":"email","title":"To"},"subject":{"type":"string","title":"Subject"},"body":{"type":"string","title":"Body"},"html":{"type":"boolean","title":"Html","default":false}},"type":"object","required":["from_address","to","subject","body"],"title":"EmailRequest"},"EmailResponse":{"properties":{"success":{"type":"boolean","title":"Success"},"message_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message Id"},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"}},"type":"object","required":["success"],"title":"EmailResponse"},"ExposeServiceRequest":{"properties":{"fqdn":{"type":"string","title":"Fqdn"},"backend_host":{"type":"string","title":"Backend Host"},"backend_port":{"type":"integer","title":"Backend Port"},"service_type":{"type":"string","title":"Service Type","default":"spa"},"auth_required":{"type":"boolean","title":"Auth Required","default":true},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description"}},"type":"object","required":["fqdn","backend_host","backend_port"],"title":"ExposeServiceRequest","description":"Request model for exposing a new service"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"HealthResponse":{"properties":{"status":{"type":"string","title":"Status"},"service":{"type":"string","title":"Service"},"build_info":{"type":"object","title":"Build Info"},"services":{"type":"object","title":"Services"}},"type":"object","required":["status","service","build_info","services"],"title":"HealthResponse","description":"Health check response"},"InterfaceCreate":{"properties":{"ifname":{"type":"string","title":"Ifname"},"owner":{"type":"string","title":"Owner","default":"ccm_edge"},"addresses":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Addresses"},"state":{"type":"string","title":"State","default":"draft"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"additionalProperties":false,"type":"object","required":["ifname"],"title":"InterfaceCreate"},"InterfaceUpdate":{"properties":{"ifname":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Ifname"},"owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner"},"addresses":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Addresses"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"additionalProperties":false,"type":"object","title":"InterfaceUpdate"},"InviteRequest":{"properties":{"email":{"type":"string","maxLength":254,"minLength":3,"title":"Email"},"display_name":{"type":"string","maxLength":64,"minLength":1,"title":"Display Name"},"role":{"type":"string","enum":["family","friend","guest"],"title":"Role"},"username_suggestion":{"type":"string","maxLength":32,"minLength":1,"title":"Username Suggestion"}},"type":"object","required":["email","display_name","role","username_suggestion"],"title":"InviteRequest"},"InviteResponse":{"properties":{"username":{"type":"string","title":"Username"},"email":{"type":"string","title":"Email"},"role":{"type":"string","title":"Role"}},"type":"object","required":["username","email","role"],"title":"InviteResponse"},"NatRuleCreate":{"properties":{"rule_type":{"type":"string","title":"Rule Type"},"match":{"type":"object","title":"Match","default":{}},"target":{"type":"object","title":"Target","default":{}},"owner":{"type":"string","title":"Owner","default":"ccm_edge"},"state":{"type":"string","title":"State","default":"draft"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["rule_type"],"title":"NatRuleCreate"},"NatRuleUpdate":{"properties":{"rule_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Rule Type"},"match":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Match"},"target":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Target"},"owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"NatRuleUpdate"},"PolicyRuleCreate":{"properties":{"priority":{"type":"integer","title":"Priority"},"selector":{"type":"object","title":"Selector"},"action":{"type":"object","title":"Action"},"owner":{"type":"string","title":"Owner","default":"ccm_edge"},"state":{"type":"string","title":"State","default":"draft"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["priority","selector","action"],"title":"PolicyRuleCreate"},"PolicyRuleUpdate":{"properties":{"priority":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Priority"},"selector":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Selector"},"action":{"anyOf":[{"type":"object"},{"type":"null"}],"title":"Action"},"owner":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Owner"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"PolicyRuleUpdate"},"RevokeResponse":{"properties":{"username":{"type":"string","title":"Username"},"disabled":{"type":"boolean","title":"Disabled"}},"type":"object","required":["username","disabled"],"title":"RevokeResponse"},"RoutingTableCreate":{"properties":{"table_id":{"type":"integer","title":"Table Id"},"table_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Table Name"},"routes":{"items":{"type":"object"},"type":"array","title":"Routes","default":[]},"state":{"type":"string","title":"State","default":"draft"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","required":["table_id"],"title":"RoutingTableCreate"},"RoutingTableUpdate":{"properties":{"table_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Table Name"},"routes":{"anyOf":[{"items":{"type":"object"},"type":"array"},{"type":"null"}],"title":"Routes"},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State"},"notes":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Notes"}},"type":"object","title":"RoutingTableUpdate"},"SuccessResponse":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"message":{"type":"string","title":"Message"},"warnings":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Warnings"}},"type":"object","required":["message"],"title":"SuccessResponse","description":"Standard success response"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"}},"securitySchemes":{"APIKeyHeader":{"type":"apiKey","in":"header","name":"X-API-Key"}}}}