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").
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
Generate types from the specification of the exact panel version you have deployed: the set of fields changes between releases.
What's nextâ
- Go SDK â a client on the Go standard library.
- Python SDK â a client on
requests. - API Specification â all endpoints in OpenAPI.
- Environment Variables â
JWT_AUTH_LIFETIMEand the rest of the settings.