Skip to main content
EN

Home / Blog / Deployment

The Clash control API in full — endpoints, dashboards and automation scripts

Deployment2026-06-011299 words3 min read
The Clash control API in full — endpoints, dashboards and automation scripts

The Mihomo core ships a RESTful API. Clash Verge's own interface talks to the core through it — understand the endpoints and you can script anything the interface can do.

Enabling it, and the security implications

external-controller: 127.0.0.1:9090
secret: "a sufficiently long random string"

Generate a random secret:

openssl rand -hex 24

Authentication

Every request carries a bearer token:

curl -H "Authorization: Bearer your-secret" http://127.0.0.1:9090/version

WebSocket endpoints (logs, traffic) accept a query parameter instead:

ws://127.0.0.1:9090/traffic?token=your-secret

The full endpoint table

MethodPathPurpose
GET/versionCore version
GET/configsCurrent configuration
PATCH/configsChange runtime settings (ports, mode and so on)
PUT/configs?force=trueReload the config file
GET/proxiesEvery node and policy group
GET/proxies/:nameDetail for one node or group
PUT/proxies/:nameChange the selected node in a group
GET/proxies/:name/delayLatency-test one node
GET/group/:name/delayTest a whole policy group
GET/connectionsAll current connections
DELETE/connectionsClose every connection
DELETE/connections/:idClose one connection
GET/rulesThe rule list currently in effect
GET/providers/proxiesAll proxy-providers
PUT/providers/proxies/:nameUpdate one provider by hand
GET/providers/rulesAll rule-providers
PUT/providers/rules/:nameUpdate a rule set by hand
GET/logsLog stream (WebSocket)
GET/trafficLive traffic (WebSocket)
GET/memoryMemory use (WebSocket)

Common operations

List every policy group and its current selection

curl -s -H "Authorization: Bearer $SECRET" \
  http://127.0.0.1:9090/proxies | jq '.proxies | to_entries[] | select(.value.type=="Selector") | {group: .key, now: .value.now}'

Switch a node

curl -X PUT \
  -H "Authorization: Bearer $SECRET" \
  -H "Content-Type: application/json" \
  -d '{"name":"HK-01"}' \
  http://127.0.0.1:9090/proxies/PROXY

Group names containing spaces or emoji need URL encoding:

GROUP=$(printf '%s' "🚀 Select" | jq -sRr @uri)
curl -X PUT -H "Authorization: Bearer $SECRET" \
  -d '{"name":"HK-01"}' \
  "http://127.0.0.1:9090/proxies/$GROUP"

Latency-test a node

curl -s -H "Authorization: Bearer $SECRET" \
  "http://127.0.0.1:9090/proxies/HK-01/delay?timeout=5000&url=http%3A%2F%2Fwww.gstatic.com%2Fgenerate_204"
# returns {"delay":123}

Change the proxy mode

curl -X PATCH -H "Authorization: Bearer $SECRET" \
  -H "Content-Type: application/json" \
  -d '{"mode":"global"}' \
  http://127.0.0.1:9090/configs

mode accepts rule, global or direct. The same endpoint also changes log-level, allow-lan and others.

Reload the configuration

curl -X PUT -H "Authorization: Bearer $SECRET" \
  -H "Content-Type: application/json" \
  -d '{"path":"/etc/mihomo/config.yaml"}' \
  "http://127.0.0.1:9090/configs?force=true"

Close all connections

curl -X DELETE -H "Authorization: Bearer $SECRET" \
  http://127.0.0.1:9090/connections

Switching nodes does not move existing connections (an established TCP connection keeps using the old node); this forces everything to reconnect.

Update a provider

# refresh the node subscription
curl -X PUT -H "Authorization: Bearer $SECRET" \
  http://127.0.0.1:9090/providers/proxies/main

# refresh a rule set
curl -X PUT -H "Authorization: Bearer $SECRET" \
  http://127.0.0.1:9090/providers/rules/cn-domain

Useful scripts

1. Switch to the lowest-latency node automatically

#!/bin/bash
# pick-fastest.sh — test, then switch to the fastest node
set -euo pipefail
API="http://127.0.0.1:9090"
SECRET="your-secret"
GROUP="AUTO"
TEST_URL="http%3A%2F%2Fwww.gstatic.com%2Fgenerate_204"

# trigger a test across the group
curl -s -H "Authorization: Bearer $SECRET" \
  "$API/group/$GROUP/delay?timeout=5000&url=$TEST_URL" > /dev/null

# find the lowest latency
BEST=$(curl -s -H "Authorization: Bearer $SECRET" "$API/proxies" \
  | jq -r --arg g "$GROUP" '
    .proxies[$g].all[] as $n
    | .proxies[$n]
    | select(.history | length > 0)
    | select(.history[-1].delay > 0)
    | "\(.history[-1].delay) \(.name)"
  ' | sort -n | head -1 | cut -d' ' -f2-)

echo "fastest node: $BEST"
curl -s -X PUT -H "Authorization: Bearer $SECRET" \
  -d "{\"name\":\"$BEST\"}" "$API/proxies/$GROUP" > /dev/null

2. Watch node health and alert when everything is down

#!/bin/bash
# health-watch.sh
API="http://127.0.0.1:9090"
SECRET="your-secret"

ALIVE=$(curl -s -H "Authorization: Bearer $SECRET" "$API/proxies" \
  | jq '[.proxies[] | select(.type != "Selector" and .type != "URLTest")
         | select(.history | length > 0)
         | select(.history[-1].delay > 0)] | length')

if [ "$ALIVE" -eq 0 ]; then
  echo "$(date '+%F %T') no usable nodes" >> /var/log/mihomo-alert.log
  # hook your notification channel in here
fi

3. List the heaviest connections

curl -s -H "Authorization: Bearer $SECRET" http://127.0.0.1:9090/connections \
  | jq -r '.connections
      | sort_by(-.download)
      | .[:10][]
      | "\(.download/1048576 | floor)MB  \(.metadata.host // .metadata.destinationIP)  \(.metadata.processPath // "-")"'

Very handy for tracking down "what is eating my data".

4. Follow the log live

# needs websocat or wscat
websocat "ws://127.0.0.1:9090/logs?token=your-secret&level=info"

Deploying a web dashboard

A dashboard is a purely static page that talks to the core through the API.

external-ui: /etc/mihomo/ui
external-ui-name: metacubexd
external-ui-url: "https://dashboard-release-address/dist.zip"

Extract the dashboard files into the directory named by external-ui, then visit:

http://core-address:9090/ui

On first open you supply the API address (http://core-address:9090) and the secret.

Option B: use a publicly hosted dashboard

A dashboard is pure frontend, so you can also use someone else's hosted page and point it at your API address and secret.

Comparing common dashboards

Three classes of dashboardmetacubexdfeature-completemodern interfaceslightly largerzashboardlightweight and good for operating from a phonethe yacd familythe classiclowest resource usesome newer features unsupported
A dashboard is just a frontend for the API — swap it any time without touching the core

A few clever uses of the API

Scheduled node switching

Move to a dedicated line at peak hours and back to an ordinary node during the day:

# crontab
0 20 * * * /usr/local/bin/switch-node.sh "IPLC-HK"
0 1  * * * /usr/local/bin/switch-node.sh "AUTO"

A keyboard shortcut (desktop)

Paired with AutoHotkey on Windows or Hammerspoon on macOS, one shortcut toggles between global and rule mode:

# switch to global
curl -X PATCH -H "Authorization: Bearer $SECRET" \
  -d '{"mode":"global"}' http://127.0.0.1:9090/configs

Feeding a monitoring system

/traffic is a WebSocket endpoint pushing upload and download rates every second. Collect it into a time-series database and you have traffic graphs.

Diagnostics

When the API will not connectIs the port listening: ss -tlnp \grep 9090Is the listening address 127.0.0.1 or 0.0.0.0 (that decides whether remote access is possible)Is the secret correct and the Authorization header formatted as Bearer xxxDoes the firewall allow 9090A 401 response — wrong secretA 404 response — wrong path; remember group names need URL encodingCannot connect while the core runs — check whether external-controller is commented out in the config

A quick check:

curl -i -H "Authorization: Bearer $SECRET" http://127.0.0.1:9090/version

A 200 with a version JSON means you are through.

In short

  • The API can do everything the interface can, including switching nodes, changing mode and reloading the config
  • secret is required, especially when listening on 0.0.0.0
  • Group names with spaces or emoji need URL encoding
  • After switching nodes, remember DELETE /connections, or existing connections keep using the old node
  • A dashboard is only a frontend; self-hosting is safer than a third-party page

Related: Mihomo as a home gateway and diagnosing traffic and connections.


Related docs

Getting Docker and WSL2 onto the host's Clash proxy
Deployment Getting Docker and WSL2 onto the host's Clash proxy

127.0.0.1 inside a container is not the host. Configuration for all three Docker scenarios — daemon pulls, build time and run time — plus two approaches for WSL2's different networking modes.

2026-06-141330 words3 min read