Clash API Advanced Guide: Automate Node Switching With YAML

Why Automate Clash Node Switching?

A manually selected proxy node is acceptable for occasional browsing, but it becomes a weak operational strategy when Clash runs continuously on a workstation, home server, router, or development machine. A node that was fast in the morning may become congested after a few hours, lose its upstream connection, or remain technically reachable while suffering from severe packet loss. If the proxy group still points to that node, applications may appear to hang even though the Clash process itself is healthy.

Clash and the Mihomo core already provide the building blocks for a more reliable setup: policy groups, latency testing, health checks, an external-controller API, and YAML configuration. The API does not replace your subscription or magically repair an unavailable server. Instead, it gives scripts and local tools a controlled way to inspect proxy states, trigger tests, select a better member, and update policies without opening the graphical client every time.

This guide focuses on a practical automation pattern. You will create a proxy group that can measure its members, expose the controller only on a protected local address, use a shell script to query the API, and apply a selection rule when the active route becomes slow or unavailable. The same ideas work with Clash Verge Rev, Mihomo Party, OpenClash, and other clients that expose a compatible Mihomo controller. Menu names differ between clients, but the YAML and HTTP concepts remain largely the same.

Tip: Automation should make a deliberate decision, not continuously switch nodes for tiny latency differences. Use a timeout threshold, a minimum improvement margin, and a reasonable cooldown period so that active connections are not interrupted by unnecessary changes.

Understand the Controller and Policy Design

The Clash API is an HTTP interface served by the core’s external controller. A typical configuration contains an address such as 127.0.0.1:9090 and a secret token. The controller can expose endpoints for proxies, groups, traffic, connections, rules, configuration reloads, and provider management. For node automation, the most important endpoint is the proxy resource: it reports the members of a policy group, their current delay values, and the currently selected member.

That convenience also creates a security boundary. Anyone who can reach an unauthenticated controller may be able to change your active proxy, inspect connection metadata, or alter runtime behavior. Bind the controller to loopback unless a remote dashboard or management host is genuinely required. If you must listen on a LAN address, use a strong secret, restrict access with a firewall, and avoid forwarding the port through your router. A controller port is an administration interface, not another ordinary proxy port.

There are two different kinds of automation to distinguish. First, a passive policy group can perform periodic health checks and choose a member according to its built-in behavior. Second, an active API script can make a more specific decision by combining delay, failure state, node name, region, or business hours. The first approach is simpler and normally preferable. The second is useful when you need custom rules, notifications, scheduled changes, or integration with monitoring software.

Choose the Right YAML Group

A url-test group periodically tests its members and normally prefers the member with the lowest measured delay. It is a good default for general browsing when all nodes provide similar access and you do not care which specific node is selected. A fallback group keeps the first healthy member in its ordered list and moves to another member only when the current one fails. This is better when route stability matters more than winning every latency test.

A load-balance group distributes requests across multiple members according to its strategy. It can be useful for parallel workloads, but it is not always the best choice for accounts or services that associate sessions with an IP address. A user who needs a stable login session should usually prefer fallback or a carefully controlled select group.

Keep the group’s purpose explicit. A group named Auto-Global might contain every subscription node, while a separate Streaming or AI group can use a smaller, tested subset. This separation prevents a script designed to improve ordinary browsing from unexpectedly moving a long-running download, video session, or API workload to a completely different region.

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

proxy-groups:
  - name: Auto-Global
    type: url-test
    url: https://www.gstatic.com/generate_204
    interval: 300
    tolerance: 80
    proxies:
      - Node-A
      - Node-B
      - Node-C

rules:
  - MATCH,Auto-Global

The example uses a short interval only to illustrate the relationship between fields. In daily use, a five-minute interval is often enough. A very aggressive interval creates extra requests, consumes resources, and may cause providers to see repeated probes as unusual traffic. The tolerance value is equally important: it prevents the group from moving to another node merely because the measured difference is insignificant.

Prepare and Test the Clash API

Before writing an automation script, confirm that the client has loaded the configuration and that the controller is responding. In Clash Verge Rev or another Mihomo GUI, locate the controller or external-controller setting, set it to a loopback address, add a secret, and reload the profile. Some clients expose these settings in a separate settings page rather than inside the profile editor. Do not assume that a port shown in a dashboard is enabled for API access; verify it with an actual request.

First, confirm the listener: Check that the address is bound to the local machine and that no other application is already using the selected port. On Linux and macOS, ss -lntp or lsof -iTCP:9090 can help. On Windows, netstat -ano provides a similar check.

Next, send an authenticated request: Use the controller URL together with the authorization header. A successful response should return JSON rather than an HTML dashboard page or a connection-refused error. Keep the token in an environment variable instead of writing it directly into a script that may be committed to a repository.

Finally, inspect the group: Query the proxy resource for the exact group name and verify that the response lists the expected members, delay values, and current selection. Names containing spaces must be URL-encoded or passed through a tool that performs encoding correctly.

Record the baseline: Test the group several times at different hours. This gives you realistic latency and failure behavior before automation begins changing routes. A node that is consistently stable at 120 milliseconds may be preferable to one that alternates between 40 and 900 milliseconds.

export CLASH_API="http://127.0.0.1:9090"
export CLASH_SECRET="replace-with-your-secret"

curl --fail --silent \
  -H "Authorization: Bearer ${CLASH_SECRET}" \
  "${CLASH_API}/proxies/Auto-Global"

Some Mihomo versions return a JSON object whose all field contains group members and whose now field contains the current selection. The exact response can vary slightly across cores and versions, so inspect the response rather than hard-coding assumptions from an old screenshot. If your group contains nested groups, the reported member may be another policy group instead of a physical node. That is expected, but your script must decide whether to inspect the outer group or the nested group.

Build a Safe Automatic Switching Script

A useful script should follow a predictable sequence: request the group state, identify candidate members, perform fresh delay tests when necessary, compare the result with a threshold, switch only when the improvement is meaningful, and record what happened. The API’s delay endpoint typically accepts a proxy name and a test URL. This is more reliable than treating an old value displayed in the group response as current, especially when the group interval is long.

Use a test URL that represents the traffic you actually care about. A lightweight HTTP response is useful for basic reachability, but it does not measure streaming quality, DNS behavior, or access to a particular service. You can maintain separate groups and test URLs for separate purposes, while avoiding large downloads or endpoints that require authentication. The probe should be quick, stable, and safe to request repeatedly.

#!/usr/bin/env bash
set -euo pipefail

API="${CLASH_API:-http://127.0.0.1:9090}"
SECRET="${CLASH_SECRET:?Set CLASH_SECRET first}"
GROUP="${CLASH_GROUP:-Auto-Global}"
TEST_URL="${CLASH_TEST_URL:-https://www.gstatic.com/generate_204}"
MAX_DELAY="${CLASH_MAX_DELAY:-800}"
MIN_IMPROVEMENT="${CLASH_MIN_IMPROVEMENT:-80}"

auth=(-H "Authorization: Bearer ${SECRET}")

group_json="$(curl --fail --silent "${auth[@]}" \
  "${API}/proxies/${GROUP}")"

current="$(printf '%s' "$group_json" | jq -r '.now')"
current_delay="$(printf '%s' "$group_json" | jq -r --arg n "$current" \
  '.proxies[$n].history[-1].delay // 99999')"

best_name=""
best_delay="$MAX_DELAY"

for node in $(printf '%s' "$group_json" | jq -r '.all[]'); do
  encoded="$(jq -rn --arg v "$node" '$v|@uri')"
  result="$(curl --fail --silent "${auth[@]}" \
    "${API}/proxies/${encoded}/delay?timeout=5000&url=$(printf '%s' "$TEST_URL" | jq -sRr @uri)")"
  delay="$(printf '%s' "$result" | jq -r '.delay // 99999')"

  if [ "$delay" -lt "$best_delay" ]; then
    best_delay="$delay"
    best_name="$node"
  fi
done

if [ -n "$best_name" ] && \
   [ "$best_delay" -lt "$current_delay" ] && \
   [ $((current_delay - best_delay)) -ge "$MIN_IMPROVEMENT" ]; then
  body="$(jq -n --arg name "$best_name" '{name: $name}')"
  curl --fail --silent --request PUT "${auth[@]}" \
    --header "Content-Type: application/json" \
    --data "$body" \
    "${API}/proxies/${GROUP}"
  printf 'Switched %s -> %s (%sms)\n' "$current" "$best_name" "$best_delay"
else
  printf 'Kept %s; best candidate was %s (%sms)\n' \
    "$current" "$best_name" "$best_delay"
fi

This script is intentionally conservative. It does not switch simply because another node is one millisecond faster. It also gives failed requests an effectively unusable delay, so a timeout cannot accidentally become the preferred result. The API’s response format and endpoint support should be checked against the Mihomo version bundled with your client; if a client uses a slightly different delay response, adjust the JSON extraction rather than removing failure handling.

There are several production improvements worth adding. Log the timestamp, current node, candidate node, measured delay, and API status code. Add a lock file so two scheduled runs cannot modify the group simultaneously. Add a cooldown file or compare the last switch time before changing anything. If the controller is unavailable, exit without touching the active configuration. When a switch occurs, send a desktop notification, write to system logs, or call a webhook so you can understand why an application changed routes.

Security warning: Never publish the controller secret in a public repository, paste it into a shared issue, or place it in a command line that is visible to other users through process listings. The secret grants control over the running core. Use a protected environment file, operating-system credential store, or restricted service configuration, and keep the controller bound to loopback whenever possible.

Schedule the Job Without Creating Flapping

On Linux or macOS, a cron entry or a user-level systemd timer can run the script every five or ten minutes. On Windows, Task Scheduler can start a PowerShell equivalent at logon and then repeat it at a fixed interval. The schedule should be slower than the group’s probe interval unless you have a clear reason to perform independent tests. Running both mechanisms every few seconds usually adds noise rather than reliability.

Run the first scheduled version in report-only mode. In that mode, the script measures candidates and records the proposed choice but does not send the PUT request. Compare the proposed decisions with real browsing, application logs, and packet behavior. A low HTTP delay does not always mean a good route: the node may have poor DNS performance, unstable WebSocket connections, or an upstream policy that blocks the service you need.

Once the decisions look sensible, enable switching and retain a rollback path. Keep a known-good node near the beginning of a fallback group, preserve the original YAML profile, and provide a simple command that restores a manually selected member. Automation is valuable only when it remains understandable and reversible during an outage.

Common Failures and Tuning Advice

Connection refused usually means the controller is disabled, the address or port is wrong, or the client restarted with a different profile. Check the listener before changing the script. 401 Unauthorized indicates a missing, malformed, or incorrect bearer token. Make sure the header is exactly in the form expected by the core and that shell quoting has not removed special characters.

404 Not Found often comes from using a group name or endpoint that does not exist in the current core. Query the general proxy list first and copy the name exactly. Spaces, slashes, non-ASCII characters, and symbols must be encoded when inserted into a URL. Using a JSON-aware tool to construct URL components is safer than manually concatenating strings.

Every candidate reports a timeout can be caused by an unsuitable test URL, blocked probe traffic, incorrect system time, broken DNS, or a controller that is reachable but not connected to the active profile. Test one node manually and compare the result with a browser or command-line request through the mixed port. Remember that a successful proxy handshake does not guarantee that every destination is reachable.

Frequent node changes are usually a policy problem rather than an API problem. Increase the interval, raise the tolerance, require a minimum delay improvement, and add a cooldown. You can also require two consecutive bad measurements before switching. For long-lived WebSocket or database sessions, prefer a fallback strategy and avoid changing the route while an important session is active.

The group changes but traffic does not may indicate that your rules point to a different group, the application bypasses Clash, or an existing connection remains pinned to the old route. Confirm the active rule in the dashboard, inspect the connections endpoint, and reconnect the application after a deliberate switch. TUN mode, system proxy mode, and per-application proxy settings can each produce different behavior.

Frequently Asked Questions

Do I need the API to use automatic node selection?

No. A well-configured url-test or fallback group can handle ordinary health checks without an external script. The API becomes useful when you need custom thresholds, notifications, schedules, service-specific tests, or integration with another monitoring system. Start with the built-in group behavior and add scripting only when the simpler policy cannot express your requirement.

Should I expose the controller to another device?

Only when there is a clear management need. A remote dashboard may require a LAN address, but the controller should then be protected by a strong secret and a firewall allowlist. Do not bind it to all interfaces merely because a tutorial uses 0.0.0.0. If remote access is necessary, a private VPN or an SSH tunnel is generally safer than directly publishing the controller port.

Which URL should the latency probe use?

Choose a small, stable endpoint that is reachable through the destinations you want to measure. A generic connectivity endpoint is fine for a global group, while a service-specific group may need a domain relevant to that service. Avoid large files, authenticated pages, and endpoints that rate-limit frequent requests. The probe measures one path, so treat it as an indicator rather than a complete quality score.

Do I need to reload YAML after every API switch?

Usually not. Selecting a member through the proxy-group API changes the running state without rewriting your profile file. A YAML reload is needed only when you change the configuration itself, such as adding nodes, changing rules, or modifying group definitions. Keep runtime selection separate from source configuration so a scheduled profile update does not unexpectedly erase your chosen policy state.

Compared with tools such as V2rayNG or Shadowrocket, which can be excellent for manual mobile connections but often require more app-specific scripting or do not expose the same desktop-friendly controller workflow, Clash provides a clearer automation surface across Windows, macOS, Linux, Android, and router deployments. Mihomo policy groups, YAML reviewability, built-in probes, and an authenticated API let you improve reliability without hiding every decision behind a proprietary interface. If you want to test this workflow with a maintained client, visit the Clash download page to get the appropriate version, then download Clash and begin with a small, observable policy group before expanding automation.