Advanced July 29, 2026 · ~16 min read

Clash External Controller API: Advanced Node Auto-Switching

Learn how to automate Clash node selection with the external-controller API, latency probes, policy-group updates, and secure scripts for reliable proxy failover in development or production.

What the Clash External Controller API Does

The Clash external-controller API is a local HTTP interface that lets another application inspect and control a running Clash or Mihomo core. Instead of opening the client GUI and manually selecting a proxy, a script can read available nodes, measure their latency, check the active policy, and change a selector group when the current route becomes unreliable. This makes the API useful for development environments, home servers, scheduled jobs, and production-like failover workflows where a human should not be required to watch the proxy panel.

The controller usually listens on a local address such as 127.0.0.1:9090. The exact port, API path, and supported endpoints vary between Clash forks and client versions, so treat the running core's documentation and API response as the final authority. Recent Mihomo-based clients generally provide a broader API surface than older Clash builds, while the basic operations remain familiar: retrieve the configuration, list proxies, test a proxy URL, and select a member inside a policy group.

There are two important concepts to separate. A proxy node is an individual outbound server, such as a Shadowsocks, Trojan, VLESS, or Hysteria2 entry. A policy group is the object that decides which outbound should be used for matching traffic. A group may be a manual selector, a URL-test group, a fallback group, or another supported strategy. Your automation should normally change the group selection rather than rewriting the entire configuration. That keeps rules, DNS settings, and provider definitions intact while limiting the change to the decision that actually needs to be made.

Security warning

Never expose an unauthenticated external controller on a public interface. Bind it to 127.0.0.1 whenever possible, set a strong secret, and restrict access with a firewall if a trusted remote machine must manage the core.

Prepare Secure Controller Access

Before writing automation, open the Clash client and locate the controller setting. Depending on the application, it may appear under General, External Controller, API, or an advanced configuration panel. Confirm the listening address and port, then define a secret token. A typical YAML fragment looks like the following:

external-controller: 127.0.0.1:9090
secret: replace-this-with-a-long-random-token

Some clients expose this setting in their graphical interface and generate the final core configuration automatically. Others expect you to edit a profile or an override file. Do not assume that changing a profile's YAML is enough: the client may merge several files, overwrite generated sections during an update, or run a separate core process with its own controller port. After saving the setting, restart or reload the core and verify that the port is actually listening.

Use an API request that does not modify state as your first test. The /version endpoint is commonly available, although the exact response fields differ by implementation:

curl -sS \
  -H "Authorization: Bearer replace-this-with-a-long-random-token" \
  http://127.0.0.1:9090/version

If the request returns a JSON object containing a version or meta field, the controller is reachable and the token is accepted. A connection refusal usually means the core is stopped, the port is wrong, or the controller is bound to another address. A 401 or 403 response normally indicates an incorrect or missing bearer token. Test these basic conditions before debugging node selection; otherwise, a network problem can look like an API problem.

Access checklist

  • Bind the controller to loopback unless remote administration is genuinely required.
  • Store the secret in an environment variable or operating-system secret store, not directly in a repository.
  • Use HTTPS or a protected SSH tunnel when a controller must be reached from another host.
  • Log status codes and short error messages, but never print the complete authorization header.
  • Confirm that the API port is separate from the mixed proxy port and the SOCKS port.

For a service account or scheduled process, create a dedicated token and give the process only the network access it needs. A local script that can change proxy policy is powerful: if an attacker obtains the token, they may redirect traffic through an untrusted node or use the controller as an internal network pivot. Rotating the token after sharing a debug log or publishing a configuration file is a sensible precaution.

Discover Policy Groups and Available Nodes

Do not hard-code a group name until you have inspected the live API response. Subscription providers frequently use names such as Proxy, 节点选择, Auto, or a region-specific label, and users may rename groups in their profiles. The controller's proxies endpoint commonly returns a JSON object in which each key is a proxy or policy name:

curl -sS \
  -H "Authorization: Bearer $CLASH_SECRET" \
  http://127.0.0.1:9090/proxies

Look for the group that carries ordinary proxy traffic. Its object often contains a type, a current now member, and a all array containing selectable members. A member may be another policy group rather than a physical node, so recursive groups are possible. The names returned by the API are authoritative; preserve their spelling, spaces, punctuation, and non-Latin characters exactly when constructing a request.

A reliable automation process should validate the target group before changing it. Check that the group exists, that it is a selector or another type that supports manual selection, and that the desired node appears in its member list. If a provider removes a node after a subscription update, a stale script should fail safely instead of selecting an unrelated member whose name happens to be similar.

Object or endpoint Typical purpose Automation consideration
/version Confirm controller reachability and core version Use as a startup health check
/configs Read active configuration metadata Useful for checking mode or profile state
/proxies List groups, nodes, and current selections Discover names instead of assuming them
/proxies/{name} Read one policy group or proxy object URL-encode names containing spaces or symbols
/proxies/{name} with PATCH Select a member in a compatible group Validate the member before sending the change
/proxies/{name}/delay Probe a proxy against a test URL Set a timeout and interpret failures conservatively

Endpoint availability and HTTP methods can differ between classic Clash, Clash Premium derivatives, and Mihomo. A common selection request uses a JSON body with the key name:

curl -sS -X PATCH \
  -H "Authorization: Bearer $CLASH_SECRET" \
  -H "Content-Type: application/json" \
  --data '{"name":"Tokyo-01"}' \
  "http://127.0.0.1:9090/proxies/Proxy"

Names containing a slash, question mark, percent sign, or Unicode characters must be URL-encoded in the path. A shell script should not concatenate untrusted text into a URL without encoding it. In Python, use a URL quoting function; in JavaScript, use encodeURIComponent. This is both a correctness issue and a small but important defense against malformed requests.

Measure Latency Without Choosing the Fastest Blindly

Latency probing is useful, but a single number does not describe the quality of a proxy. The delay endpoint typically asks Clash to request a test URL through a selected node and returns a measured delay. Choose a stable HTTPS endpoint that responds with a small body, set an explicit timeout, and use the same URL for all candidates. If the test URL is blocked in a particular region, every healthy node may appear to fail, so the test target should match the traffic environment you are trying to protect.

curl -sS -G \
  -H "Authorization: Bearer $CLASH_SECRET" \
  --data-urlencode "url=https://www.gstatic.com/generate_204" \
  --data-urlencode "timeout=5000" \
  "http://127.0.0.1:9090/proxies/Tokyo-01/delay"

Use more than latency when making a decision. A node with a 120 millisecond response may be better than a 70 millisecond node that drops connections, expires TLS sessions, or becomes congested after a few minutes. A practical score can combine recent probe results, failure count, response time, and a cooldown period. For example, prefer nodes below a latency threshold, then rank them by a moving median rather than the last individual measurement.

Do not probe every node every few seconds. Aggressive testing creates unnecessary traffic, consumes provider resources, and can cause rate limits or false failures. A small pool of candidates checked every one to five minutes is usually enough for desktop use. Production services may use a shorter interval only when the operational need justifies it. Keep the last known good node and avoid switching for a single transient timeout; require two consecutive failures or a sustained error window before failover.

Practical rule

Separate “slow” from “dead.” A slow node may still support an important long-lived connection, while a dead node should trigger failover. Use hysteresis: switch away after repeated failures, but switch back only after the recovered node has passed several healthy probes.

Remember that the API probe measures a route from the Clash core to the test endpoint, not necessarily from the application to its real destination. DNS mode, TUN mode, IPv6 preference, TLS negotiation, and the selected rule can all affect the result. For high-value workloads, combine API delay measurements with application-level checks such as an authenticated health request, a small download, or a connection attempt to the actual service.

Build a Safe Automatic Switching Workflow

A good failover script has four stages: observe, decide, act, and verify. First read the current group and collect candidate health data. Next decide whether the current node violates a defined policy. Then send one selection request. Finally read the group again and perform a real connectivity check. Keeping these stages separate makes the script easier to test and prevents a failed probe from immediately causing a cascade of selection changes.

  1. Observe the current state. Record the group name, active member, timestamp, and recent error count. If the group is already changing because of a built-in URL-test or fallback strategy, do not fight it with a second controller.
  2. Filter candidates. Exclude disabled, unavailable, duplicate, and recently failed nodes. Respect region, protocol, bandwidth, and subscription constraints rather than selecting any node with the smallest delay.
  3. Apply a threshold. Switch only when the active node has failed repeatedly, exceeded a latency budget for a defined period, or failed an application-level check.
  4. Change the policy group. Send a PATCH request with the exact member name and wait for a successful HTTP response. Do not reload the complete configuration for a simple selector change.
  5. Verify and record. Read the group again, test the protected destination, and write a redacted event containing the old node, new node, reason, and result.

The following Python example shows the core pattern. It is intentionally conservative and should be adapted to the API version exposed by your client:

import os
import time
import requests

BASE = os.getenv("CLASH_API", "http://127.0.0.1:9090")
SECRET = os.environ["CLASH_SECRET"]
GROUP = os.environ.get("CLASH_GROUP", "Proxy")
TEST_URL = "https://www.gstatic.com/generate_204"

session = requests.Session()
session.headers.update({"Authorization": f"Bearer {SECRET}"})

def get_group():
    response = session.get(f"{BASE}/proxies/{requests.utils.quote(GROUP, safe='')}",
                           timeout=4)
    response.raise_for_status()
    return response.json()

def select_node(node):
    response = session.patch(
        f"{BASE}/proxies/{requests.utils.quote(GROUP, safe='')}",
        json={"name": node},
        timeout=4,
    )
    response.raise_for_status()

state = get_group()
current = state.get("now")
candidates = [name for name in state.get("all", []) if name != current]

if candidates:
    selected = candidates[0]
    select_node(selected)
    time.sleep(1)
    print({"previous": current, "selected": selected})

For real use, add a delay probe, candidate validation, retry limits, and a lock so that two scheduled executions cannot switch the group simultaneously. A file lock, database advisory lock, or single-worker queue can prevent race conditions. Add a cooldown after every switch, because a script that alternates between two nodes will create instability even when both nodes are usable.

Use dry-run mode during development. In dry-run mode, the script reads groups and probes candidates but never sends a PATCH request. Log the proposed decision and compare it with manual observations for at least a day. Once enabled, expose a manual override such as an environment flag or a separate control file. Automation should be easy to pause when a provider performs maintenance, a destination changes behavior, or a user needs a stable node for a long download.

For production, run the process with a restricted operating-system user, a read-only code directory, and a secret supplied through the service manager. Set request timeouts on every HTTP operation, handle malformed JSON, and distinguish a controller outage from a node outage. If the controller itself is unavailable, preserve the current proxy state rather than blindly assuming that all nodes are dead. Rate-limit notifications and include a recovery message so operators know when automatic failover has stopped.

Troubleshoot and Maintain the Automation

A successful HTTP response does not guarantee that traffic has switched as expected. First confirm that the target group is the group used by the relevant rules. If the rule points to Streaming while the script changes Proxy, the API request can be perfectly valid and still have no visible effect. Next inspect the Clash connections view and verify the policy, selected outbound, and final rule for a real request.

If the API returns “method not allowed,” check the client family and endpoint documentation. Some older implementations support a different method or path, while some groups are read-only because they are managed automatically. If a selection request returns a validation error, compare the requested node with the live all list. Subscription updates may remove a node, rename it, or replace it with a nested group.

When probes fail for every node, test the controller's own network path, DNS resolution, system clock, and the selected test URL. A captive portal, broken IPv6 route, expired certificate, or blocked probe domain can produce a complete set of false negatives. Try a second controlled endpoint and compare the result with a normal request made through the Clash client. Never disable TLS verification as a permanent workaround for a probe failure.

Keep the automation compatible with profile updates. Provider-generated configurations may replace group names, add prefixes, or change a manual selector into a URL-test group. A startup validation step should report missing groups clearly and stop without modifying anything. Review logs after every subscription refresh, and retain a short history of node changes so that repeated failures can be correlated with a provider outage or a local network change.

Compared with lightweight proxy switchers that offer only a manual list, or isolated health-check scripts that cannot change the active policy, Clash provides one place for rules, DNS behavior, node groups, connection visibility, and controlled API updates. That integration avoids the common problem of a script selecting a node while the client continues routing traffic through another group. If you need reliable node failover with transparent diagnostics and a well-defined controller boundary, download Clash for free and build the workflow around the client that already manages your proxy traffic.

Choose a Better Clash Subscription

Test providers clearly, manage profiles safely, and compare real-world performance with a reliable Clash client.

Download Clash for Windows