Xray gRPC API Guide: Automate Node Selection and Traffic Stats

Learn how to use Xray-core's gRPC API to read traffic counters and manage outbound handlers. This practical guide covers StatsService and HandlerService, a u…

At a glance

This guide is for operators who want to monitor Xray traffic and automate decisions without repeatedly editing a client’s node list by hand. It explains how to enable the local gRPC API, read StatsService counters, and use RoutingService balancer overrides for controlled node selection. You’ll also see where HandlerService fits, how to protect the API, and how to avoid switching nodes based on noisy or misleading measurements.

What the Xray gRPC API Can—and Cannot—Do

The Xray API is a control interface for a running core. It is not a subscription service, a replacement for client configuration, or a mechanism that automatically discovers whether a server is healthy. Your application must supply the decision logic: query measurements, compare them against a policy, and ask Xray to perform an operation when the evidence is strong enough.

Three services are especially relevant to automated node management. StatsService reports traffic counters when statistics are enabled. HandlerService manages runtime handlers, including adding, removing, or altering supported inbounds and outbounds. RoutingService provides routing-related operations, including overriding a balancer target in cores that support the relevant API method. The installed Xray binary and its API definitions determine which methods are available; do not assume that every fork or older build exposes an identical API.

Enable API services Collect traffic counters Evaluate node health Override balancer target Verify new traffic

A useful distinction is that HandlerService changes runtime handlers, while a balancer override changes which existing outbound a balancer selects. If the goal is to switch among a fixed set of nodes, a routing balancer is often a cleaner design than repeatedly deleting and recreating outbounds. HandlerService can still be useful for controlled runtime changes, but it does not magically rewrite every routing rule or subscription entry.

Configure a Local API Inbound and Statistics

The API is reached through an inbound that accepts the local gRPC connection. A common example uses 127.0.0.1:10085, the dokodemo-door protocol, and a routing rule that sends requests entering the API inbound to the API outbound. The address and port below are examples; check that port 10085 is unused and make sure your client connects to the same address and port.

{
  "api": {
    "tag": "api",
    "services": [
      "HandlerService",
      "RoutingService",
      "StatsService"
    ]
  },
  "stats": {},
  "policy": {
    "levels": {
      "0": {
        "statsUserUplink": true,
        "statsUserDownlink": true
      }
    },
    "system": {
      "statsInboundUplink": true,
      "statsInboundDownlink": true
    }
  },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "port": 10085,
      "protocol": "dokodemo-door",
      "settings": {
        "address": "127.0.0.1"
      },
      "tag": "api"
    }
  ],
  "outbounds": [
    {
      "protocol": "freedom",
      "tag": "api"
    }
  ],
  "routing": {
    "rules": [
      {
        "type": "field",
        "inboundTag": ["api"],
        "outboundTag": "api"
      }
    ]
  }
}

This is a configuration example, not a universal drop-in replacement for a client-generated file. If the active configuration already has an API inbound, API outbound, or routing rules, merge the required fields rather than adding duplicate tags. Tags must be unique where the configuration requires them to be, and the API routing rule must not be shadowed by an earlier rule. Restart or reload the core using the method supported by the application, then inspect startup logs for JSON errors, a port bind failure, or an API service initialization problem.

The stats object enables the statistics facility, while policy flags determine which categories of traffic are counted. System-level inbound counters and user-level counters serve different purposes. Inbound counters can help compare traffic entering particular listeners; user counters require the relevant user identity and policy level to be configured. A counter that is absent is not necessarily evidence of zero traffic: first check whether the matching statistics option is enabled and whether that traffic has a tag or user identity that can be counted.

Local API access

Example host
127.0.0.1
Example port
10085
Inbound protocol
dokodemo-door
API tag
api

Keep the listener on loopback unless you have a separately designed, secured management network.

Traffic counters

Facility
StatsService
Global switch
stats: {}
System flags
statsInboundUplink / Downlink
User flags
statsUserUplink / Downlink

Enable only the counter categories your monitoring workflow needs.

For node selection, configure candidate outbounds with stable, distinct tags such as node-a and node-b. A balancer’s selector should match only the intended candidates, and a routing rule should direct the traffic you want to manage to that balancer. Keep the API inbound rule separate: API control requests must reach the API handler, not be sent through the node-selection balancer. Check your Xray configuration format and core build before copying balancer fields, since supported routing options can differ between core versions.

Build a Repeatable Node-Selection Workflow

Start with a manual baseline before automating changes. Verify that each candidate outbound works on its own, that the intended user traffic matches the balancer rule, and that API calls can reach the local listener. Only then add a controller. The steps below describe a cautious workflow; menu labels for enabling a custom core configuration vary between client versions, so confirm that the running core is actually using the file you edited.

  1. Confirm the core

    Run xray version for the binary you intend to control, and compare it with the executable path shown in the client’s core startup log. Confirm that the build exposes the API services and methods your controller needs.

  2. Define candidates

    Assign each outbound a unique tag, such as node-a, node-b, and node-c. Configure a balancer selector that matches only these tags, then route the relevant traffic to the balancer. Keep direct, block, and API outbounds outside the candidate set.

  3. Enable counters

    Add StatsService, the stats object, and the policy flags for the counters you will read. Start Xray and check the log for configuration errors. Send a small, known amount of test traffic before treating an empty result as a monitoring failure.

  4. Read and compare

    Connect to 127.0.0.1:10085 and query counters at a fixed interval, for example every 10 seconds. Record each counter’s name and value with a timestamp. Compare changes between samples rather than comparing lifetime totals from nodes that have been active for different lengths of time.

  5. Switch with a guard

    When the installed Xray API supports balancer target overrides, call the corresponding RoutingService method for the configured balancer and a valid candidate tag. Log the requested target, API response, and time. Confirm that new connections use the target; existing connections may continue on their original outbound.

StatsService exposes named counters rather than a ready-made “best node” score. Common counter names identify a scope and direction, for example outbound>>>node-a>>>traffic>>>uplink or outbound>>>node-a>>>traffic>>>downlink. User counters may include a user identifier. Treat the returned counter name as an identifier, not as a guaranteed schema across all builds. Your collector should tolerate missing counters, new names, and nodes that have not yet carried traffic.

Design Selection Logic That Does Not Flap

Traffic counters help answer “how much data passed through this tagged outbound?” They do not, by themselves, answer “which node is fastest right now?” A busy node can have a large counter because it has served more traffic, while an idle node can have a small counter despite having excellent latency. For active selection, combine counter deltas with a deliberate health check and, where useful, a repeatable latency or transfer test.

Keep the controller’s policy small and observable. For example, collect samples every 10 seconds, require three consecutive failed probes before marking a candidate unhealthy, and wait at least 60 seconds after a switch before making another automatic switch. These are starting values, not universal thresholds: increase the observation window on unstable networks and test against the actual traffic pattern. Do not select a new node because of one delayed response or one short-lived traffic burst.

Decision rule: Require evidence before moving traffic

Switch only when the current node fails a defined health condition for several consecutive checks, or when a measured alternative is consistently better across multiple samples. Use a cooldown and keep the previous target available so that a failed change can be reversed quickly.

A practical controller can use a simple state machine: healthy, suspect, unhealthy, and recovering. One failed probe moves a node to suspect; repeated failures move it to unhealthy. A successful probe should not immediately restore it to healthy if the service is still unstable—require several successful checks over a recovery window. This prevents a node that alternates between working and failing from repeatedly taking traffic away and receiving it back.

Use the RoutingService balancer override for selecting among already configured candidates when supported by the running core. Confirm the actual balancer tag and target tag, and record errors instead of silently assuming a successful switch. A target accepted by the API still does not prove that user traffic matches the intended balancer rule. Verify with a new connection and, if available, a controlled request whose destination and expected route are known.

HandlerService is a separate option for runtime handler management. Adding or removing an outbound can be useful for a system that creates candidates dynamically, but it increases the number of moving parts: the controller must provide a valid operation for the outbound protocol, preserve tag consistency, and account for active connections and routing references. Do not remove an outbound merely because a counter is currently zero. It could be idle but still needed, or the counter might be disabled. For a fixed subscription-based node list, keep candidates in the configuration and switch the balancer target instead.

Secure the API and Troubleshoot Failures

The local API is a powerful control surface. A loopback listener is a safer default because it limits access to processes on the same host, but it is not an authentication system: another local process with access to the port may be able to issue control requests. Do not bind the API to 0.0.0.0 or a public interface for convenience. If remote management is necessary, put it behind a carefully controlled private management path with firewall restrictions and appropriate transport protection; do not assume the Xray API inbound itself provides encryption or access control.

When a request fails, identify which layer is failing before changing node settings. A gRPC connection error often points to a listener, port, or service mismatch. A successful connection with no matching counters points toward disabled policy flags, an unexpected tag, or no traffic in the selected scope. A successful balancer request with no visible route change points toward the balancer configuration, routing-rule order, or reuse of an existing connection.

Frequently Asked Questions

Can StatsService tell me which node has the least load?

No. It reports configured traffic counters, not remote CPU usage or a universal load score. Compare counter deltas with explicit health checks and controlled measurements; an idle counter alone does not mean a node is better or worse.

Does switching the balancer target move existing connections?

Do not assume it does. A target change affects routing decisions made after the change; established connections may remain attached to their original outbound. Test with a new connection and retain a rollback target.

Should I use HandlerService to replace nodes after every check?

Usually not for a fixed set of subscription nodes. Define candidate outbounds and use a supported balancer override. Reserve HandlerService operations for cases that genuinely need runtime handler creation or alteration.

Can I expose port 10085 so another machine can manage Xray?

A loopback-only listener is the safer default. Do not expose the control port directly to an untrusted network. If remote management is required, design a restricted, protected management path and verify firewall rules before enabling it.

Download v2rayN