Skip to main content

x3ui

PyPI Python CI License

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)

Source distribution for x3ui 2.0.0
File Size Uploaded
x3ui-2.0.0.tar.gz 185.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for x3ui 2.0.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

2.0.1

2 release files

This release

2.0.0 This release

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page