x3ui
Automate your 3x-ui panel from Python. Issue users, renew subscriptions, check usage, clean up expired accounts — without clicking through the web UI.
Unofficial project. Not affiliated with the 3x-ui developers.
pip install x3ui
Connect
from x3ui import Panel
panel = Panel("https://panel.example.com:2053/yourpath")
panel.login("admin", "your-password")
The URL is exactly what you type in the browser, including the port and your panel's secret path.
For anything that runs unattended, use an API token instead — mint one under Settings → Security → API Token:
panel = Panel("https://panel.example.com:2053/yourpath", token="...")
A token is a full-admin credential. Keep it out of your source: read it from the environment.
Issue a user
from datetime import timedelta
panel.clients.add(
"alice",
inbound_ids=[3],
total_gb=100,
expires=timedelta(days=30),
limit_ip=3,
)
for link in panel.clients.links("alice"):
print(link)
The name is whatever identifies the user to you — the panel calls this field "email" but does not care whether it looks like one.
Traffic is gigabytes, expiry takes a timedelta from now or a datetime. Leave either out for unlimited. UUIDs, passwords and keys are generated by the panel.
links() returns the connection URLs for every inbound the user is on — that is what you send them. For a subscription URL instead, use the user's subId:
panel.clients.sub_links(panel.clients.get("alice").client.sub_id)
Don't know your inbound IDs? List them:
for inbound in panel.inbounds.list():
print(inbound.id, inbound.remark, inbound.protocol, inbound.port)
Renew and top up
panel.clients.extend(["alice", "bob"], days=30, gigabytes=100)
Works on any number of users at once, and accepts negative values to take time or traffic away. Users on unlimited time or traffic are left alone rather than being converted to limited.
Renewing someone who ran out and got auto-disabled re-enables them.
To wipe a counter instead of adding to it:
panel.clients.reset_traffic("alice")
panel.clients.bulk_reset_traffic(["alice", "bob"])
Check usage
usage = panel.clients.traffic("alice")
print(usage.up, usage.down, usage.total, usage.expiry_time)
up and down are bytes used, total is the quota (0 means unlimited).
Who is connected right now:
print(panel.clients.online())
Where a user is connecting from:
print(panel.clients.ips("alice"))
Everyone at once, for a dashboard or a report:
for client in panel.clients.list():
used = client.traffic.up + client.traffic.down
print(client.email, used, client.enable)
Change and revoke
panel.clients.update("alice", limit_ip=1000, limit_hwid=10)
panel.clients.update("alice", password="new-secret", auth="new-secret")
panel.clients.update("alice", enable=False)
Only the fields you pass change; everything else stays as it is. Rotating a secret invalidates the user's existing links — send them fresh ones from links().
Cutting someone off, one or many:
panel.clients.bulk_disable(["alice", "bob"])
panel.clients.bulk_enable(["alice"])
panel.clients.delete("alice")
panel.clients.bulk_delete(["alice", "bob"], keep_traffic=True)
keep_traffic preserves the accounting rows after the user is gone, which matters if you bill from them.
Moving a user between inbounds without recreating them:
panel.clients.attach("alice", [7, 9])
panel.clients.detach("alice", [3])
Clean up
print(panel.clients.delete_depleted())
print(panel.clients.delete_orphans())
The first removes everyone out of traffic or past their expiry date; the second removes users left behind when their inbound was deleted. Both are destructive and report how many they took.
Server and Xray
status = panel.server.status()
print(status.cpu, status.mem.current, status.xray.state)
panel.server.restart_xray()
When something goes wrong
from x3ui import NotAuthenticated, X3uiError
try:
panel.clients.add("alice", inbound_ids=[3])
except X3uiError as error:
print(error.message)
X3uiError carries the message the panel itself would show — "email already in use", "port already in use", and so on. NotAuthenticated is raised when the session expired; log in again and retry. Connection problems raise httpx.TimeoutException.
Scripts that run on a schedule
import os
from datetime import datetime, timedelta, timezone
from x3ui import Panel
deadline = (datetime.now(timezone.utc) + timedelta(days=3)).timestamp() * 1000
with Panel(os.environ["PANEL_URL"], token=os.environ["PANEL_TOKEN"]) as panel:
expiring = [
client.email
for client in panel.clients.list()
if 0 < client.expiry_time < deadline
]
if expiring:
panel.clients.extend(expiring, days=30)
Used as a context manager, Panel closes its connection on exit. Token auth needs no login call and no session to expire, which is what you want from cron.
Self-signed certificate on the panel? Pass verify_ssl=False. Slow server? Pass timeout=60.
Anything else
The methods above cover day-to-day work. Nodes, hosts, backups, Xray config and the rest of the panel's 186 endpoints are available too:
from x3ui._generated.api.nodes import get_panel_api_nodes_list
print(get_panel_api_nodes_list.sync(client=panel.raw).obj)
Requires Python 3.10 or newer. Development notes and how to regenerate against your own panel are in CONTRIBUTING.md.
License
MIT
Release files for x3ui 2.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| x3ui-2.0.1.tar.gz | 190.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| x3ui-2.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 831.7 kB
Release files / x3ui-2.0.1.tar.gz
| Download URL | x3ui-2.0.1.tar.gz |
|---|---|
| Size | 190.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bd24cbe928c9ecc461fe87132c891f77ec6a7259231fc080871736a483dba621
|
|
BLAKE2b-256 checksum How to use checksums |
5fd62b5a41a4ce720cdfb99fcfe51ba639c023cb8fe411d166f59984f523c825
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.
Transparency logRelease files / x3ui-2.0.1-py3-none-any.whl
| Download URL | x3ui-2.0.1-py3-none-any.whl |
|---|---|
| Size | 640.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ad9d8c9250d43d0b4e258a26178021cabd69b63f1b4406c2b6f99057be105e54
|
|
BLAKE2b-256 checksum How to use checksums |
73abb99b43529caca56484170fff681b8f198486a5908a8bffd10ed8ef273e40
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.
Transparency log