x3ui
Typed Python client for the 3x-ui panel API, generated from OpenAPI.
Unofficial project. Not affiliated with the 3x-ui developers.
Installation
pip install x3ui
Requires Python 3.10 or newer.
Quick start
from x3ui import Panel
with Panel("https://panel.example.com:2053") as panel:
panel.login("admin", "admin")
for inbound in panel.inbounds.list():
print(inbound.id, inbound.remark, inbound.protocol, inbound.port)
print(panel.clients.online())
Base URL must include the port and the panel's base path if one is configured, for example https://panel.example.com:2053/mypath.
Two layers
Panel is the high-level interface. It authenticates, unwraps the panel's {success, msg, obj} envelope, and raises X3uiError when the panel reports a failure — so methods return the payload directly.
Everything the facade does not wrap is reachable through the generated layer, which covers all 186 operations. Pass panel.raw as the client:
from x3ui._generated.api.hosts import get_panel_api_hosts_list
response = get_panel_api_hosts_list.sync(client=panel.raw)
print(response.success, response.obj)
The generated layer is regenerated from the specification, so its names track the panel's routes and can change between releases. The facade is hand-written and stable.
Authentication
Username and password
panel = Panel("https://panel.example.com:2053")
panel.login("admin", "admin")
login fetches a CSRF token before sending the credentials. The panel rejects unauthenticated POST requests — including the login request itself — with an empty 403 when the X-CSRF-Token header is absent, so the order matters. The session cookie is kept afterwards; keep using the same Panel object.
With two-factor authentication enabled, pass the current OTP code. It rotates every 30 seconds, so generate it immediately before logging in:
panel.login("admin", "admin", two_factor_code="123456")
API token
panel = Panel("https://panel.example.com:2053", token="YOUR_API_TOKEN")
Create a token in the panel under Settings → Security → API Token. Requests are sent with an Authorization: Bearer <token> header, and CSRF does not apply. The plaintext token is shown once at creation; the panel stores only a hash.
An API token is a full-admin credential — treat it like the panel password.
Common operations
Inbounds
panel.inbounds.list()
panel.inbounds.get(1)
panel.inbounds.set_enable(1, False)
panel.inbounds.reset_traffic(1)
list() returns typed Inbound objects with client_stats attached.
Clients
panel.clients.list()
panel.clients.get("alice@example.com")
panel.clients.traffic("alice@example.com")
panel.clients.links("alice@example.com")
panel.clients.sub_links("abcd1234")
panel.clients.online()
panel.clients.ips("alice@example.com")
The email is the client identifier in 3x-ui, not necessarily a real address.
Creating a client and attaching it to inbounds in one call:
panel.clients.add(
"alice@example.com",
inbound_ids=[3, 5],
total_gb=53687091200,
expiry_time=1735689600000,
limit_ip=2,
)
Per-protocol secrets (UUID, password, keys) are generated by the panel when omitted. Byte counts are bytes; timestamps are Unix milliseconds, where 0 means unlimited.
Changing and removing:
panel.clients.update("alice@example.com", total_gb=107374182400, enable=True)
panel.clients.reset_traffic("alice@example.com")
panel.clients.attach("alice@example.com", [7, 9])
panel.clients.detach("alice@example.com", [5])
panel.clients.delete("alice@example.com", keep_traffic=True)
update replaces the fields you pass; anything omitted is left as stored.
Server
panel.server.status()
panel.server.new_uuid()
panel.server.restart_xray()
Error handling
from x3ui import NotAuthenticated, X3uiError
try:
panel.clients.get("nobody")
except NotAuthenticated:
panel.login("admin", "admin")
except X3uiError as error:
print(error.operation, error.message)
X3uiError is raised when the panel answers with success: false; NotAuthenticated is the subclass raised when the message points at an expired or missing session. Network timeouts surface as httpx.TimeoutException.
Configuration
panel = Panel(
"https://panel.example.com:2053",
token="YOUR_API_TOKEN",
timeout=60.0,
verify_ssl=False,
)
verify_ssl=False disables certificate verification — only for panels with self-signed certificates, never in production. Extra keyword arguments are passed through to the underlying httpx client.
For direct access to the HTTP session:
panel.raw.get_httpx_client()
Async
The facade is synchronous. Every generated operation also has asyncio and asyncio_detailed variants — import them under an alias so they do not shadow the standard library module:
import asyncio as aio
from x3ui import Panel
from x3ui._generated.api.inbounds import get_panel_api_inbounds_list
async def main():
panel = Panel("https://panel.example.com:2053", token="YOUR_API_TOKEN")
result = await get_panel_api_inbounds_list.asyncio(client=panel.raw)
print(result.obj)
aio.run(main())
An async facade is not implemented yet.
Endpoint groups
Generated modules live under x3ui._generated.api.<group>, mirroring the tags in the specification: authentication, clients, inbounds, server, settings, xray_settings, nodes, hosts, backup, api_tokens, subscription_server, subscription_balancers, web_socket.
Module names follow <method>_<path>, so POST /panel/api/clients/add becomes x3ui._generated.api.clients.post_panel_api_clients_add.
Typing
The panel returns every payload inside obj, and its specification leaves most of those undescribed. Before generation, tools/infer_obj_schemas.py reads the response and request examples in the document and writes back the schemas it can infer, typing 61 responses and 43 request bodies that would otherwise be Any.
Inferred object schemas are hoisted into components/schemas under names derived from the route, so the generated models read as ServerStatus, ClientsListItem and ClientsAddRequest rather than GetPanelApiServerStatusResponse200Obj:
status = panel.server.status()
print(status.cpu, status.xray.state)
The remaining 101 responses carry no example, stay Any, and come back as plain dicts and lists.
Regenerating
./regenerate.sh
Or pull a fresh document from a live panel first:
PANEL_URL=https://panel.example.com:2053/basepath PANEL_TOKEN=... ./regenerate.sh --fetch
The script resets the servers entry before writing the file. A specification fetched from a live panel contains that panel's base path, which is a security-relevant secret — never commit it.
Regeneration only replaces x3ui/_generated. The facade in x3ui/__init__.py and x3ui/panel.py is hand-written and survives.
Generated against 3x-ui version 3.x. Endpoints may differ on other panel versions.
Contributing
The most valuable contribution is describing more of the specification: the panel documents obj for only a fraction of its endpoints, and every schema added there turns a dict into a typed model for everyone. Adding facade coverage for endpoints that currently need the generated layer is equally welcome.
License
MIT
Release files for x3ui 2.0.0
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.0.tar.gz | 185.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| x3ui-2.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 819.4 kB
Release files / x3ui-2.0.0.tar.gz
| Download URL | x3ui-2.0.0.tar.gz |
|---|---|
| Size | 185.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
ecc35519b574dce67102f91796d47809397c8d0f387dfbac59c59ce12bcf087e
|
|
BLAKE2b-256 checksum How to use checksums |
7b3b5705409d0bdb9a416d23f1b56a8f1ab015409324dd35f79ea2c2f8706e12
|
| 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.0-py3-none-any.whl
| Download URL | x3ui-2.0.0-py3-none-any.whl |
|---|---|
| Size | 633.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a73afe6301200a74941981beed80038c84eddb3e81e356b0ba4eca5e85ccfda5
|
|
BLAKE2b-256 checksum How to use checksums |
de911eb3f58444c04e4c375cc66c86632e57906dc287425eac1f8b9f1b653f94
|
| 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