Metadata-Version: 2.5
Name: x3ui
Version: 2.0.1
Summary: Typed Python client for the 3x-ui panel API, generated from OpenAPI
Project-URL: Homepage, https://github.com/vahellame/x3ui
Project-URL: Repository, https://github.com/vahellame/x3ui
Project-URL: Issues, https://github.com/vahellame/x3ui/issues
Project-URL: Changelog, https://github.com/vahellame/x3ui/blob/main/CHANGELOG.md
Author-email: Vadim Reznichenko <vahellame@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: 3x-ui,api-client,openapi,vpn,xray,xui
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: attrs>=22.2.0
Requires-Dist: httpx<0.29.0,>=0.23.0
Requires-Dist: python-dateutil>=2.8.0
Requires-Dist: typing-extensions>=4.0.0
Description-Content-Type: text/markdown

# x3ui
[![PyPI](https://img.shields.io/pypi/v/x3ui)](https://pypi.org/project/x3ui/)
[![Python](https://img.shields.io/pypi/pyversions/x3ui)](https://pypi.org/project/x3ui/)
[![CI](https://github.com/vahellame/x3ui/actions/workflows/ci.yml/badge.svg)](https://github.com/vahellame/x3ui/actions/workflows/ci.yml)
[![License](https://img.shields.io/pypi/l/x3ui)](https://github.com/vahellame/x3ui/blob/main/LICENSE)

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](https://github.com/MHSanaei/3x-ui) developers.

```
pip install x3ui
```

## Connect

```python
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:

```python
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

```python
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`:

```python
panel.clients.sub_links(panel.clients.get("alice").client.sub_id)
```

Don't know your inbound IDs? List them:

```python
for inbound in panel.inbounds.list():
    print(inbound.id, inbound.remark, inbound.protocol, inbound.port)
```

## Renew and top up

```python
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:

```python
panel.clients.reset_traffic("alice")
panel.clients.bulk_reset_traffic(["alice", "bob"])
```

## Check usage

```python
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:

```python
print(panel.clients.online())
```

Where a user is connecting from:

```python
print(panel.clients.ips("alice"))
```

Everyone at once, for a dashboard or a report:

```python
for client in panel.clients.list():
    used = client.traffic.up + client.traffic.down
    print(client.email, used, client.enable)
```

## Change and revoke

```python
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:

```python
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:

```python
panel.clients.attach("alice", [7, 9])
panel.clients.detach("alice", [3])
```

## Clean up

```python
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

```python
status = panel.server.status()
print(status.cpu, status.mem.current, status.xray.state)

panel.server.restart_xray()
```

## When something goes wrong

```python
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

```python
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:

```python
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](CONTRIBUTING.md).

## License

MIT
