Skip to content

Local Runtime HTTP API

When Leaf is running it exposes a local HTTP server on 127.0.0.1:<api_port> (default 10001 on desktop, configurable via preferences). This API is the canonical source of truth for live telemetry — every SDK's ApiClient is just a typed wrapper around it.

Security: the server only listens on the loopback interface. It has no authentication — anything running as the current user on the same machine can talk to it.


Base URL

http://127.0.0.1:<api_port>

Read the port from LeafPreferences.api_port (Rust / FFI) or .getApiPort() (Android / Java).


Runtime management

POST /api/v1/runtime/reload

Re-read the configuration from disk without dropping the TUN device. Equivalent to calling reloadLeaf().

POST /api/v1/runtime/shutdown

Request the runtime to stop. The core process continues running; you can then stop it explicitly via stopCore / shutdown_core.

GET /api/v1/runtime/check_connectivity

Perform a one-shot TCP check through the currently selected outbound.

{ "tcp_ms": 128.7 }

Stats & usage

GET /api/v1/runtime/stat/json

Returns a JSON array of active connections with bytes sent/received, endpoints, and duration. stat/html returns a human-readable HTML dashboard for debugging.

GET /api/v1/runtime/usage/json?tag=<name>

Aggregated bytes in/out for an outbound or inbound tag.

{ "bytes_sent": 12345678, "bytes_recvd": 87654321 }

Alternative: ?userId=<uuid> returns usage for a specific client UUID.


Logs

GET /api/v1/runtime/logs/json?limit=<n>&offset=<m>

Returns a window of in-memory logs (requires memory_logger = true in preferences).

{ "messages": ["...", "..."] }

GET /api/v1/runtime/logs/html

Same content rendered for a browser.

POST /api/v1/runtime/logs/clear

Drop the in-memory ring buffer.


Outbound groups

Leaf supports select and failover outbound groups. The API below exposes both.

GET /api/v1/app/outbound/list

List all configured groups.

{
  "outbounds": [
    { "tag": "OUT",    "type": "select" },
    { "tag": "US",     "type": "failover" }
  ]
}

GET /api/v1/app/outbound/selects?outbound=<tag>

List the candidates inside a select group and which one is currently active.

{
  "outbounds": [
    { "name": "us-ams-1", "is_selected": true  },
    { "name": "us-ams-2", "is_selected": false }
  ]
}

GET /api/v1/app/outbound/select?outbound=<tag>

Return the currently selected member of a group.

{ "selected": "us-ams-1" }

POST /api/v1/app/outbound/select?outbound=<tag>&select=<name>

Switch to a different member.

POST /api/v1/app/outbound/select?outbound=OUT&select=us-ams-2

GET /api/v1/app/outbound/failover/status?outbound=<tag>

Return the actor table for a failover group (each actor = a candidate endpoint + its last ping).

{
  "actors": [
    { "name": "us-ams-1", "ping_ms": 42.1, "is_selected": true  },
    { "name": "us-ams-2", "ping_ms": 78.4, "is_selected": false }
  ]
}

POST /api/v1/app/outbound/failover/healthcheck?outbound=<tag>

Force an immediate re-check of a single failover group. Returns 200 when triggered, 404 when the group does not exist.

POST /api/v1/app/outbound/failover/healthcheck/all

Force an immediate re-check of every failover group. Returns 200 when triggered or 202 when accepted asynchronously.


Per-outbound health & activity

GET /api/v1/runtime/outbound/<tag>/health

Active TCP + UDP probing. Returns { "tag": "...", "tcp_ms": 48.2, "udp_ms": null }. SDKs use this to paint latency indicators.

GET /api/v1/runtime/outbound/<tag>/last_peer_active

Last Unix timestamp at which a peer on this outbound exchanged bytes.

{ "tag": "us-ams-1", "last_peer_active": 1735689600 }

GET /api/v1/runtime/outbound/<tag>/since_last_peer_active

Seconds since the last peer activity.

{ "tag": "us-ams-1", "since_last_peer_active": 14 }

Typed SDK access

SDK Class Import
Android com.github.shiroedev2024.leaf.android.library.ApiClient OkHttp + Gson
Desktop Rust leaf_sdk_desktop::ApiClient Tokio + Hyper
Desktop Java com.github.shiroedev2024.leaf.desktop.api.ApiClient Apache HttpClient 5 + Gson
FFI (none — call via libcurl etc.) any HTTP client

Every SDK wraps the same endpoints with identical return types, so examples in the Android / Desktop guides apply 1:1 here. For instance, api.getOutboundHealth("OUT") returns the JSON described in /api/v1/runtime/outbound/<tag>/health.


Error responses

Every non-success status code returns a plain text message. SDK clients throw IOException / anyhow::Error / LeafException and surface the text to you.

Status Meaning
200 OK
202 Accepted (asynchronous healthcheck)
404 Unknown outbound tag
500 Internal runtime error — check the logs endpoint

See also