Skip to main content

Python SDK

There is no official XLADA SDK for Python. The XLADA API is identical to the Remnawave API, so a working client takes a couple of dozen lines on requests, and the types come from the OpenAPI specification.

Compatibility

For Remnawave there are community Python libraries built from the same specification; in theory they apply to XLADA too, but we have not officially verified them with XLADA. The example below depends only on requests.

Installation​

python3 -m venv .venv
source .venv/bin/activate
pip install requests

For asynchronous code httpx will do, but for scripts and bots synchronous requests is enough.

Client​

The token is a JWT from POST /api/auth/login or an API token from POST /api/tokens. Both are passed in the Authorization: Bearer header. The X-Forwarded-For and X-Forwarded-Proto: https headers are needed when calling the panel directly: without them the panel's middleware drops the connection.

import os

import requests

PANEL_URL = os.environ.get("XLADA_PANEL_URL", "https://panel.example.com")
API_TOKEN = os.environ.get("XLADA_API_TOKEN", "")


class XladaClient:
def __init__(self, base_url: str = PANEL_URL, token: str = API_TOKEN) -> None:
self.base_url = base_url.rstrip("/")
self.session = requests.Session()
self.session.headers.update(
{
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
"X-Forwarded-For": "127.0.0.1",
"X-Forwarded-Proto": "https",
}
)

def request(self, method: str, path: str, **kwargs) -> dict:
response = self.session.request(method, f"{self.base_url}{path}", timeout=30, **kwargs)
response.raise_for_status()
return response.json()

Authentication​

If you have no API token, get an administrator JWT and pass it to the client:

def login(username: str, password: str) -> str:
response = requests.post(
f"{PANEL_URL}/api/auth/login",
json={"username": username, "password": password},
timeout=30,
)
response.raise_for_status()
return response.json()["response"]["accessToken"]


client = XladaClient(token=login("admin", "password"))

A long-lived API token is created by POST /api/tokens — in the UI that is Settings → API Tokens. The finished token is shown once, in the response.token field:

created = client.request("POST", "/api/tokens", json={"name": "bot", "expiresInDays": 365})
print(created["response"]["token"])

Listing users​

Paginated output: start is the offset, size is the page size (1–1000, 25 by default).

page = client.request("GET", "/api/users", params={"start": 0, "size": 25})

for user in page["response"]["users"]:
print(user["username"], user["status"], user["expireAt"])

print("total:", page["response"]["total"])

Creating a user​

POST /api/users accepts username (3–36 characters: Latin letters, digits, _, -) and a required expireAt in ISO 8601 format.

created = client.request(
"POST",
"/api/users",
json={
"username": "john_doe",
"status": "ACTIVE",
"expireAt": "2026-01-17T15:38:45.065Z",
"trafficLimitBytes": 0,
"trafficLimitStrategy": "NO_RESET",
},
)

print(created["response"]["shortUuid"])

Getting a node​

A single node — by uuid; the node list — GET /api/nodes.

node = client.request("GET", f"/api/nodes/{node_uuid}")["response"]

print(node["name"], node["address"], node["port"], node["isConnected"])
tip

To check that the panel is reachable before you start, use GET /api/auth/status — it requires no authorization.

warning

Do not store the API token in a repository. Take it from an environment variable or a CI secret, and revoke unneeded tokens via DELETE /api/tokens/{uuid}.

What's next​