Skip to main content

TypeScript SDK

XLADA has no separate published SDK under the name XLADA. That does not get in the way: the fork's API is identical to the Remnawave API — the same paths, the same request and response schemas. So Remnawave types and contracts are compatible, and the HTTP client is assembled in a few lines.

Installation​

The examples below are fine with the standard fetch, which ships with Node.js 18+ and in the browser. If you are more used to axios:

npm install axios

The official Remnawave SDK — @remnawave/backend-contract — contains only types and zod schemas, with no HTTP client:

npm install @remnawave/backend-contract

In the panel sources the same contracts library lives in apps/backend/libs/contract (the frontend copy is in apps/frontend/vendor/backend-contract) and is called @xlada/backend-contract. It is not published to npm: include it as a local repository dependency ("@xlada/backend-contract": "file:./vendor/backend-contract").

Version match

Tie the contract version to the panel version. The contract and the panel evolve on different cycles, so the "latest" version of the types does not always describe your exact server.

Basic client​

The panel expects Authorization: Bearer <token>. If you call the panel directly, from the internal network, add the proxy headers — the panel's middleware drops the connection without X-Forwarded-For and X-Forwarded-Proto: https.

const PANEL_URL = process.env.XLADA_PANEL_URL ?? 'https://panel.example.com';
const API_TOKEN = process.env.XLADA_API_TOKEN ?? '';

async function api<T>(path: string, init: RequestInit = {}): Promise<T> {
const res = await fetch(`${PANEL_URL}${path}`, {
...init,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${API_TOKEN}`,
'X-Forwarded-For': '127.0.0.1',
'X-Forwarded-Proto': 'https',
...init.headers,
},
});

if (!res.ok) {
throw new Error(`${res.status} ${res.statusText}: ${await res.text()}`);
}

return (await res.json()) as T;
}

Authentication​

There are two ways to get a token.

Administrator JWT — lives for a limited time (JWT_AUTH_LIFETIME, from 12 to 168 hours) and is issued by POST /api/auth/login:

curl -sX POST https://panel.example.com/api/auth/login \
-H 'Content-Type: application/json' \
-d '{"username": "admin", "password": "password"}'

The response contains accessToken:

{
"response": {
"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}

API token — for scripts and integrations. Created by POST /api/tokens (in the UI, Settings → API Tokens) and passed in the same Authorization header:

curl -sX POST https://panel.example.com/api/tokens \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"name": "ci", "expiresInDays": 365}'

The finished token arrives in the response.token field and is shown once. The list of available scopes is returned by GET /api/tokens/scopes.

Listing users​

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

interface GetUsersResponse {
response: {
users: Array<{
id: number;
username: string;
status: 'ACTIVE' | 'DISABLED' | 'LIMITED' | 'EXPIRED';
shortUuid: string;
expireAt: string;
}>;
total: number;
};
}

const { response } = await api<GetUsersResponse>('/api/users?start=0&size=25');

for (const user of response.users) {
console.log(user.username, user.status, user.expireAt);
}
console.log(`total: ${response.total}`);

Creating a user​

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

const created = await api<{ response: { id: number; shortUuid: string } }>(
'/api/users',
{
method: 'POST',
body: JSON.stringify({
username: 'john_doe',
status: 'ACTIVE',
expireAt: '2026-01-17T15:38:45.065Z',
trafficLimitBytes: 0,
trafficLimitStrategy: 'NO_RESET',
}),
},
);

console.log(created.response.shortUuid);

Getting a node​

A single node — by its uuid; the list — with no parameters:

const { response: node } = await api<{
response: { name: string; address: string; port: number | null; isConnected: boolean };
}>(`/api/nodes/${nodeUuid}`);

console.log(node.name, `${node.address}:${node.port}`, node.isConnected);

Where to get types​

The full specification — 162 paths and 29 tags — is on the API Specification page and in the static/openapi.json file of the panel repository. From it you can generate .d.ts:

npx openapi-typescript ./openapi.json -o ./src/xlada-api.d.ts
tip

Generate types from the specification of the exact panel version you have deployed: the set of fields changes between releases.

What's next​