API Reference - FrankoonG/hy2scale GitHub Wiki
π English | δΈζ | νκ΅μ΄
HY2 SCALE exposes a REST API under http://<host>:5565/scale/api/. Every endpoint except POST /api/login requires a session token obtained from login.
POST /api/login
Body:
{
"username": "admin",
"password": "<sha256-hex-of-plaintext>"
}The password is SHA-256 hashed on the client before transmission; the server only ever stores and compares the hash.
Response:
{
"token": "hex-session-token",
"force_password_change": false
}force_password_change: true indicates you are still on the default admin/admin credentials β the web UI will force a password change before showing any page.
Subsequent requests pass the token as a Bearer header:
Authorization: Bearer <token>
Long-lived EventSource endpoints (notably /api/graph-layout/stream) additionally accept the token as a ?token=<hex> query parameter because EventSource can't set custom headers.
Returns this node's identity and Hysteria 2 server configuration:
{
"node_id": "ea1b2adb",
"name": "sg-home",
"exit_node": true,
"hy2_user_auth": false,
"compat": false,
"limited": false,
"server": {
"listen": "0.0.0.0:5565",
"password": "<hex>",
"tls_cert": "/data/tls/default.crt",
"tls_key": "/data/tls/default.key"
},
"version": "1.3.0"
}Replaces the node config. Only the fields you include are modified.
{ "tx_bytes": 1234567890, "rx_bytes": 9876543210 }Returns a flat array of top-level peers, each with a nested children tree for peers visible through nested discovery. Self is always first with direction: "local" and is_self: true.
[
{
"name": "ea1b2adb",
"direction": "local",
"latency_ms": 0,
"is_self": true
},
{
"name": "jp",
"direction": "outbound",
"connected": true,
"latency_ms": 35,
"nested": true,
"version": "1.3.0",
"tx_rate": 0,
"rx_rate": 0,
"children": [
{ "name": "jp-r1", "direction": "outbound", "latency_ms": 73 }
]
}
]| Method | Path |
|---|---|
| GET | /api/clients |
| POST | /api/clients |
| PUT | /api/clients/{name} |
| DELETE | /api/clients/{name} |
| PUT |
/api/clients/{name}/disable β {"disabled": bool}
|
Create body:
{
"name": "jp",
"addr": "jp.example.com:5565",
"addrs": ["jp.example.com:5565", "jp2.example.com:5565"],
"password": "<hy2-password>",
"sni": "jp.example.com",
"insecure": false,
"ca": "",
"max_tx": 0,
"max_rx": 0
}| Method | Path | Purpose |
|---|---|---|
| GET | /api/peers/{name}/peers |
fetch a peer's cached sub-peer list |
| PUT | /api/peers/{name}/nested |
toggle nested flag for this peer |
Nested body:
{ "enabled": true }{name} may be a bare peer name (direct peer) or a /-separated path for sub-peers (us/us-east, kr/kr-r1/kr-r1-a β¦).
Generic proxy list (SOCKS5, HTTP, Shadowsocks):
| Method | Path |
|---|---|
| GET | /api/proxies |
| POST | /api/proxies |
| PUT | /api/proxies/{id} |
| DELETE | /api/proxies/{id} |
Create body (SOCKS5 / HTTP / SS share this schema):
{
"id": "socks5",
"protocol": "socks5",
"listen": "0.0.0.0:1080",
"enabled": true,
"tls_cert": ""
}For Shadowsocks also include "method": "aes-256-gcm".
Each has a dedicated config endpoint pair:
| Protocol | GET / PUT |
|---|---|
| L2TP | /api/l2tp |
| IKEv2 | /api/ikev2 |
| WireGuard | /api/wireguard |
PUT triggers hot reload β the service stops and restarts with the new config.
| Method | Path |
|---|---|
| GET |
/api/wireguard (peers inside) |
| POST | /api/wireguard/peers |
| PUT | /api/wireguard/peers/{name} |
| DELETE | /api/wireguard/peers/{name} |
| GET |
/api/wireguard/peers/{name}/config (text/plain WireGuard .conf) |
| GET |
/api/wireguard/qr?peer={name} (PNG QR code) |
| POST |
/api/wireguard/generate-key (returns {private_key, public_key}) |
| Method | Path |
|---|---|
| GET | /api/users |
| POST | /api/users |
| PUT | /api/users/{id} |
| DELETE | /api/users/{id} |
| PUT | /api/users/{id}/toggle |
| PUT | /api/users/{id}/reset-traffic |
Create body:
{
"username": "alice",
"password": "<plaintext or hashed>",
"exit_via": "us/us-east",
"exit_paths": ["us/us-east"],
"exit_mode": "direct",
"traffic_limit_gb": 500,
"expiry": "2026-12-31T00:00:00Z",
"enabled": true
}| Method | Path |
|---|---|
| GET | /api/sessions |
| DELETE |
/api/sessions/{id} β kick, 60 s reconnection lockout |
| Method | Path |
|---|---|
| GET | /api/rules |
| POST | /api/rules |
| PUT | /api/rules/{id} |
| DELETE | /api/rules/{id} |
| PUT | /api/rules/{id}/toggle |
Create body:
{
"id": "netflix",
"name": "Netflix",
"type": "domain",
"targets": ["netflix.com"],
"exit_via": "us",
"exit_paths": ["us"],
"exit_mode": "direct",
"enabled": true
}type is "ip" or "domain".
| Method | Path |
|---|---|
| GET | /api/rules/tun-mode |
| PUT | /api/rules/tun-mode |
Body:
{ "enabled": true, "mode": "mixed" }mode: "mixed" (rule-matched targets via TUN, others via proxy) or "full" (all traffic via TUN).
| Method | Path |
|---|---|
| GET | /api/tls |
| POST |
/api/tls/import (PEM in body) |
| POST |
/api/tls/import-path (file paths in body) |
| POST |
/api/tls/generate (self-signed or CA) |
| POST |
/api/tls/sign (sign with existing CA) |
| GET |
/api/tls/{id}/pem (text/plain cert PEM) |
| DELETE | /api/tls/{id} |
POST /api/tls/generate body:
{
"id": "vpn-cert",
"name": "vpn.sg.example",
"domains": ["vpn.sg.example"],
"days": 730,
"is_ca": false
}The Nodes-page graph stores node coordinates server-side so every logged-in session sees the same layout.
| Method | Path | Purpose |
|---|---|---|
| GET | /api/graph-layout |
fetch current layout { key: {x, y} }
|
| PUT | /api/graph-layout |
replace layout with a new map |
| GET | /api/graph-layout/stream |
SSE stream β emits the current layout on connect, then every mutation |
SSE events are JSON objects with the same shape as the GET response.
| Method | Path |
|---|---|
| GET | /api/backup |
| POST | /api/restore |
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /api/build-id |
no | sha256 of the embedded index.html; used by the SPA auto-reload on deploy |
| GET | /api/build-info |
yes | full build info: {version, license, repository, go_deps[], natives[]}
|
/api/build-info drives the License + native components panel in Settings β Upgrade.
| Method | Path | Body |
|---|---|---|
| PUT | /api/settings/password |
{current_password, new_username?, new_password?} (all SHA-256) |
| GET | /api/settings/ui |
returns port, base path, DNS, session timeout |
| PUT | /api/settings/ui |
update UI settings |
| Method | Path | Purpose |
|---|---|---|
| POST | /api/upgrade |
multipart upload of a .tar.gz containing the new binary. Server swaps the file and restarts. |
{ "ports": [5565, 1701, 500] }Response:
{ "results": { "5565": true, "1701": false, "500": false } }true means the port is free on the host.
The following endpoints are used by the relay protocol itself for inter-node coordination. They do not use web-UI auth β access control is the Hysteria 2 tunnel password.
GET /api/internal/peers β peer list for reverse nested discovery
These aren't intended for external automation β they're documented only for protocol implementers.