Metadata-Version: 2.5
Name: x3ui
Version: 1.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)

Typed Python client for the 3x-ui panel API, generated from OpenAPI.

Unofficial project. Not affiliated with the [3x-ui](https://github.com/MHSanaei/3x-ui) developers.

## Installation

```
pip install x3ui
```

Requires Python 3.9+.

## Quick start

```python
from x3ui import AuthenticatedClient
from x3ui.api.inbounds import get_panel_api_inbounds_list

client = AuthenticatedClient(base_url="https://panel.example.com:2053", token="YOUR_API_TOKEN")

result = get_panel_api_inbounds_list.sync(client=client)
print(result.success, result.obj)
```

Base URL must include the port and the panel's base path if you configured one, for example `https://panel.example.com:2053/mypath`.

## Authentication

The panel supports two schemes.

### API token (recommended)

Create a token in the panel under Settings → Security → API Token, or through the API, then pass it to `AuthenticatedClient`. Requests are sent with an `Authorization: Bearer <token>` header.

```python
from x3ui import AuthenticatedClient

client = AuthenticatedClient(base_url="https://panel.example.com:2053", token="YOUR_API_TOKEN")
```

Creating a token programmatically:

```python
from x3ui.api.api_tokens import post_panel_api_setting_api_tokens_create
from x3ui.models.post_panel_api_setting_api_tokens_create_body import PostPanelApiSettingApiTokensCreateBody

body = PostPanelApiSettingApiTokensCreateBody(name="automation", scope="admin", expires_at=0)
result = post_panel_api_setting_api_tokens_create.sync(client=client, body=body)
```

`scope` is `admin`, `monitor`, or `node-sync`. `expires_at` is a future Unix timestamp in milliseconds; `0` means no expiry. The plaintext token is returned only once, at creation — the panel stores only a hash.

### Username and password

Log in with the panel admin credentials, then keep using the same `Client` instance. The session cookie the panel sets is stored in the client's underlying HTTP session and sent automatically on every subsequent call.

```python
from x3ui import Client
from x3ui.api.authentication import post_login
from x3ui.api.inbounds import get_panel_api_inbounds_list
from x3ui.models.post_login_body import PostLoginBody

client = Client(base_url="https://panel.example.com:2053")

login = post_login.sync(
    client=client,
    body=PostLoginBody(username="admin", password="admin", two_factor_code=""),
)

if not login.success:
    raise RuntimeError(login.msg)

result = get_panel_api_inbounds_list.sync(client=client)
print(result.obj)
```

Pass an empty string as `two_factor_code` when two-factor authentication is disabled on the panel. When it is enabled, pass the current OTP code — it rotates every 30 seconds, so generate it immediately before logging in.

Reuse the same `client` object for everything that follows. Creating a second `Client`, or calling `with_headers` / `with_cookies` / `with_timeout` after logging in, produces a new instance with a fresh HTTP session that does not carry the session cookie, and calls will come back unauthenticated.

#### CSRF token for write operations

Session-based callers must send an `X-CSRF-Token` header on unsafe requests (POST, DELETE). Mint one after logging in and attach it to the existing session:

```python
from x3ui.api.authentication import get_csrf_token

csrf = get_csrf_token.sync(client=client)
client.get_httpx_client().headers["X-CSRF-Token"] = csrf.obj
```

Setting the header on the underlying HTTP client keeps the session cookie intact, which `with_headers` would not. Bearer token callers skip this step entirely — CSRF is not enforced for token-authenticated requests.

#### Logging out

```python
from x3ui.api.authentication import post_logout

post_logout.sync(client=client)
```

## Common operations

### List all clients

```python
from x3ui.api.clients import get_panel_api_clients_list

result = get_panel_api_clients_list.sync(client=client)
```

For large panels use `get_panel_api_clients_list_paged` instead.

### Look up a client by email

```python
from x3ui.api.clients import get_panel_api_clients_get_email

result = get_panel_api_clients_get_email.sync("user@example.com", client=client)
```

The email is the client identifier in 3x-ui, not a real address.

### Get traffic for a client

```python
from x3ui.api.clients import get_panel_api_clients_traffic_email

result = get_panel_api_clients_traffic_email.sync("user@example.com", client=client)
```

### Get subscription links for a client

```python
from x3ui.api.clients import get_panel_api_clients_links_email

result = get_panel_api_clients_links_email.sync("user@example.com", client=client)
```

### Reset a client's traffic counter

```python
from x3ui.api.clients import post_panel_api_clients_reset_traffic_email

result = post_panel_api_clients_reset_traffic_email.sync("user@example.com", client=client)
```

### Delete a client

```python
from x3ui.api.clients import post_panel_api_clients_del_email

result = post_panel_api_clients_del_email.sync("user@example.com", client=client, keep_traffic=0)
```

Pass `keep_traffic=1` to keep the accumulated traffic statistics after removal.

### List clients that are currently online

```python
from x3ui.api.clients import post_panel_api_clients_onlines

result = post_panel_api_clients_onlines.sync(client=client)
```

### Get a single inbound

```python
from x3ui.api.inbounds import get_panel_api_inbounds_get_id

result = get_panel_api_inbounds_get_id.sync(1, client=client)
```

### Server status

```python
from x3ui.api.server import get_panel_api_server_status

result = get_panel_api_server_status.sync(client=client)
```

Returns CPU, memory, uptime, network counters and Xray state.

## Async usage

Every endpoint module exposes `asyncio` and `asyncio_detailed` alongside the sync variants. Import them under an alias to avoid shadowing the standard library module:

```python
import asyncio as aio

from x3ui import AuthenticatedClient
from x3ui.api.clients import get_panel_api_clients_list

async def main():
    client = AuthenticatedClient(base_url="https://panel.example.com:2053", token="YOUR_API_TOKEN")
    result = await get_panel_api_clients_list.asyncio(client=client)
    print(result.obj)

aio.run(main())
```

## Detailed responses

The plain `sync` and `asyncio` functions return the parsed model, or `None` when the status code is undocumented. Use the `_detailed` variants when you need the status code, headers or raw bytes:

```python
from x3ui.api.server import get_panel_api_server_status

response = get_panel_api_server_status.sync_detailed(client=client)

print(response.status_code)
print(response.headers)
print(response.parsed)
print(response.content)
```

## Error handling

By default, undocumented status codes yield `None`. To raise instead:

```python
client = AuthenticatedClient(
    base_url="https://panel.example.com:2053",
    token="YOUR_API_TOKEN",
    raise_on_unexpected_status=True,
)
```

The raised exception is `x3ui.errors.UnexpectedStatus`, which carries `status_code` and `content`. Network timeouts surface as `httpx.TimeoutException`.

## Client configuration

```python
import httpx

client = AuthenticatedClient(
    base_url="https://panel.example.com:2053",
    token="YOUR_API_TOKEN",
    timeout=httpx.Timeout(30.0),
    verify_ssl=False,
    follow_redirects=True,
    headers={"User-Agent": "my-bot/1.0"},
)
```

`verify_ssl=False` disables certificate verification. Only use it against panels with self-signed certificates, never in production. To pin a custom CA, pass a path to the certificate file instead.

For anything not exposed directly, reach the underlying httpx client:

```python
raw = client.get_httpx_client()
```

## Endpoint groups

Endpoints live under `x3ui.api.<group>`, mirroring the tags in the specification:

| Group | Contents |
| --- | --- |
| `authentication` | login, logout, CSRF token, 2FA |
| `clients` | CRUD, traffic, groups, IP limits, HWIDs, bulk operations |
| `inbounds` | CRUD, enable/disable, fallbacks, traffic resets, import/export |
| `server` | status, Xray control, certificates, metrics, database |
| `settings` | panel settings |
| `xray_settings` | Xray core configuration |
| `nodes` | multi-node management |
| `hosts` | host entries |
| `backup` | backup and restore |
| `api_tokens` | token management |
| `subscription_server` | subscription service |
| `subscription_balancers` | subscription balancers |
| `web_socket` | websocket endpoints |

Module names follow the pattern `<method>_<path>`, so `POST /panel/api/clients/add` becomes `x3ui.api.clients.post_panel_api_clients_add`.

## Known limitations

Response payloads arrive as `{success, msg, obj}` where `obj` is typed `Any`, because the upstream specification does not describe it. You get typed envelopes but untyped payloads.

Some request bodies are empty models for the same reason. Where a body model has no fields, the corresponding operation cannot be fully expressed through this client yet.

Do not run Python from inside the installed package directory. The package contains a `types.py` module that shadows the standard library `types` and causes a circular import.

## Regenerating

The client is generated from `openapi.json` in this repository:

```
pip install openapi-python-client
openapi-python-client generate --path openapi.json --meta none --output-path x3ui --overwrite
```

Generated against 3x-ui version 3.x. Endpoints may differ on other panel versions.

## Contributing

Improvements to `openapi.json` are the most valuable contribution: adding `operationId` values produces readable function names, and describing `obj` schemas makes responses properly typed. Open an issue or a pull request.

## License

MIT
