VMess, VLESS, Trojan, and Shadowsocks: Protocol Comparison & Use Cases

Automate Xray proxy management with its gRPC API. Learn how HandlerService adds and removes outbound handlers, while StatsService reports traffic usage. Incl…

Xray’s gRPC API can change a running core without requiring a full configuration-file edit and restart. The HandlerService can add or remove inbound and outbound handlers, while StatsService can query traffic counters. The important distinction is that changing an outbound is not the same as routing traffic through it: you must also have suitable routing rules and enable the counters you want to monitor.

At a glance

This guide is for operators who want to manage Xray outbounds and inspect traffic from scripts or an API client. You’ll learn how to expose the API on a loopback address, add and remove a SOCKS outbound with the Xray command-line API, enable and query statistics, and protect the management interface. The JSON fragments are examples to adapt to an existing configuration, not a complete replacement config.

What the Xray API can do—and what it cannot

Xray’s API is a set of gRPC services that communicate with a running core. It is separate from the local proxy protocols that clients use to carry user traffic. For example, an application connecting to a SOCKS inbound on port 10808 is not calling the management API just because both services run on the same machine.

HandlerService manages runtime handlers. Its operations include adding and removing inbound or outbound handlers. An outbound is a configured way for Xray to send traffic onward, such as a freedom direct connection, a blackhole discard connection, or an outbound that connects to a SOCKS proxy. Adding one makes it available to the running core; it does not, by itself, make any connection use it.

StatsService queries counters that Xray has recorded. It does not infer traffic totals from the network interface, and it cannot report a useful counter that was never enabled. A zero result may mean no traffic has passed through that handler, but it can also mean that the relevant statistics policy was not enabled.

127.0.0.1:10085
Example local API endpoint
2 services
HandlerService + StatsService

The API listener and services are configured in the Xray core configuration, not by toggling a proxy switch in v2rayN or v2rayNG. Those clients manage core settings and connections, but API availability depends on the core configuration actually being loaded. Confirm which Xray executable and configuration file your service or client starts before testing an API command.

Configure a loopback API listener

Start by binding the API to a local interface. The example below registers the two services used in this guide, creates a dokodemo-door inbound on port 10085, and routes requests received on that inbound to Xray’s internal API handler.

{
  "api": {
    "tag": "api",
    "services": [
      "HandlerService",
      "StatsService"
    ]
  },
  "inbounds": [
    {
      "listen": "127.0.0.1",
      "port": 10085,
      "protocol": "dokodemo-door",
      "settings": {
        "address": "127.0.0.1"
      },
      "tag": "api"
    }
  ],
  "routing": {
    "rules": [
      {
        "type": "field",
        "inboundTag": [
          "api"
        ],
        "outboundTag": "api"
      }
    ]
  }
}

This is a fragment showing the API-related parts. Merge it with the rest of your configuration rather than replacing existing inbounds, routing, or api objects wholesale. JSON objects cannot contain two separate keys with the same name and expect both to work; combine their arrays and properties deliberately. Keep the API inbound tag and the API routing rule consistent.

Binding to 127.0.0.1 means a process on the same host can connect, while a machine on the network cannot ordinarily reach that IPv4 loopback address. Do not change the listener to 0.0.0.0 merely to make a remote script work. If remote management is necessary, place the API behind a controlled private network or secure tunnel and restrict access with firewall rules. The gRPC API should be treated as a privileged control interface, not as a public service.

After merging the fragment, validate the configuration using the same Xray binary and configuration path used by the running service. Restart the core to load the API listener and services. If the port is already occupied, Xray cannot bind it; check the startup log and choose a free local port, then use that same port in subsequent API commands.

Add and remove an outbound at runtime

For a concrete example, this outbound connects to a SOCKS5 proxy running at proxy.example.net:1080. The hostname, port, username, and password below are placeholders. Replace them with values from a proxy you are authorized to use, and protect any file containing credentials.

{
  "tag": "managed-proxy-01",
  "protocol": "socks",
  "settings": {
    "servers": [
      {
        "address": "proxy.example.net",
        "port": 1080,
        "users": [
          {
            "user": "replace-with-user",
            "pass": "replace-with-password"
          }
        ]
      }
    ]
  }
}

Save the object as outbound.json. The file holds one outbound object, not a complete Xray configuration. From the directory containing the Xray executable and the file, use the API command-line interface:

xray api ado --server=127.0.0.1:10085 --tag=managed-proxy-01 --config=outbound.json

The ado operation asks HandlerService to add an outbound. Use a unique tag; routing and statistics refer to the tag, so duplicate or ambiguous names make later checks harder. A successful command means the core accepted the handler, but it does not prove that a user’s traffic is reaching it.

To remove the runtime outbound, use its tag:

xray api rmo --server=127.0.0.1:10085 --tag=managed-proxy-01

Removal affects the running core. Do not assume a runtime change has rewritten the configuration file on disk or will survive a core restart. If the outbound is meant to be part of the durable service configuration, record the intended change in the configuration-management process as well. Conversely, if it is meant to be temporary, make sure automation can recreate it after a restart.

Conclusion: adding a handler does not select it

Before testing the new outbound, verify that the applicable routing rule sends the intended traffic to managed-proxy-01. An accepted API request proves the handler was added, not that a browser, inbound, or destination is using it.

Hands-on: add, verify, and remove a managed outbound

Use a test core or a maintenance window before changing a production instance. Keep an existing known-good outbound available so you can restore service if the new proxy is unreachable. The following sequence uses the example endpoint and tag; substitute the port and tag from your configuration if they differ.

  1. Enable the API

    Merge the API service, loopback inbound, and routing rule into the active configuration. Restart the core and confirm in its startup log that it loaded the intended file and bound to port 10085.

  2. Prepare the object

    Save a valid outbound object as outbound.json. Check the SOCKS address, port, authentication fields, and the unique tag. Avoid committing real credentials to a shared configuration repository.

  3. Add the outbound

    Run xray api ado --server=127.0.0.1:10085 --tag=managed-proxy-01 --config=outbound.json from a trusted local process. Check the command result and core log for parsing or handler errors.

  4. Test the route

    Use an existing or deliberately configured routing rule that targets managed-proxy-01. Test a controlled request, then query its traffic counter. Do not treat successful API communication as a successful proxy connection.

  5. Remove or retain

    If the outbound was temporary, run the rmo command with the same tag and confirm the intended route no longer depends on it. If it should persist, update the managed configuration through your normal deployment process.

When the command fails, first separate local API reachability from outbound connectivity. A connection-refused error on 127.0.0.1:10085 points to the listener, port, or service startup. A request accepted by the API followed by an outbound connection failure points instead to the SOCKS server, credentials, DNS, or routing. Changing the API listener will not repair an upstream proxy authentication error.

Enable and query traffic statistics

Statistics collection must be enabled in the core configuration. Add an empty stats object if one is not already present, and enable outbound counters in the system policy. If your configuration already has a policy object, merge the settings into it instead of creating a duplicate top-level key.

{
  "stats": {},
  "policy": {
    "system": {
      "statsOutboundUplink": true,
      "statsOutboundDownlink": true
    }
  }
}

Restart the core after changing persistent settings. Then generate traffic through the outbound you want to measure. Xray’s commonly used outbound counter names have this form:

Query the counters with the command-line API tool:

xray api statsquery --server=127.0.0.1:10085 --pattern=outbound>>>managed-proxy-01>>>traffic

The response contains matching statistic names and values. Use the exact tag in the query; a misspelled tag can make an otherwise healthy outbound appear to have no data. Traffic counters are byte counts, not a speed test. To estimate a rate, take two readings separated by a known interval and divide the difference by the elapsed seconds. For example, a difference of 1,000,000 bytes over 10 seconds is approximately 100,000 bytes per second, before accounting for reporting intervals and other traffic.

Some API query interfaces provide a reset option. Resetting is useful when a monitoring job needs interval totals, but it changes the counter baseline for every other observer of that statistic. Prefer non-resetting queries for dashboards that compute deltas from successive readings. If you do reset counters, document the reset schedule and avoid having multiple independent jobs reset the same counters.

Per-user counters are a separate option from per-outbound counters. They require user statistics to be enabled for the relevant policy level, and the traffic must be associated with a configured user identity. Do not assume that enabling outbound statistics also creates user-level statistics. Conversely, user counters do not tell you which routing rule selected a particular outbound unless you collect and correlate additional information.

Secure and automate API access

Because HandlerService can change the running core, an exposed API endpoint can have operational consequences. Keep the listener on loopback when the automation runs on the same host. Run the automation under a dedicated operating-system account with only the filesystem and process permissions it needs, and protect configuration files that contain upstream credentials. Avoid printing full credential-bearing JSON objects to CI logs or shell history.

For remote automation, connect through a properly controlled private network or encrypted management tunnel, and restrict the source addresses with host and network firewall rules. Do not publish the gRPC port directly to the public internet. Network isolation is not a substitute for authentication or authorization where the deployment provides those controls; verify the capabilities of the actual Xray version and surrounding management layer rather than assuming that loopback binding alone protects a remotely reachable service.

Make automation idempotent. Before adding an outbound, check whether the intended tag is already present or make the job handle an “already exists” response safely. Record the requested tag, operation, timestamp, and result without recording passwords or UUIDs. Apply a timeout, retry only transient failures, and alert when a handler change succeeds but expected traffic counters remain flat. A flat counter may indicate no test traffic, a routing mismatch, or disabled statistics—not necessarily a failed API.

Finally, treat runtime state and durable configuration as two related but distinct things. A restart can discard an outbound added only through the API, while a configuration deployment can replace runtime state according to the service’s startup behavior. Keep a source of truth for intended outbounds and routing, and test the full lifecycle: core startup, API registration, runtime addition, route selection, counter query, removal, and recovery after restart.

Xray gRPC API FAQ

I added the outbound successfully, but why is its counter still zero?

First send a test request through a routing rule that targets the outbound’s exact tag. Then confirm statsOutboundUplink and statsOutboundDownlink are enabled, the core was restarted after the policy change, and the query uses the same tag.

Does an API-added outbound survive a restart?

Do not rely on a runtime addition being written back to the configuration file. Store persistent changes in the configuration-management source and verify the outbound is recreated after a controlled restart.

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

A loopback listener is intended for local access. For remote management, use a restricted private path or secure tunnel, apply firewall limits, and verify the API’s protection model. Do not expose the management port publicly as a shortcut.

Does StatsService report live bandwidth in megabits per second?

It reports counters, typically as byte totals. Read the counter twice over a measured interval, calculate the difference per second, and convert units if needed. Keep the query non-resetting when multiple monitors share the same counters.

Download v2rayN