Skip to main content

Go SDK

There is no official XLADA SDK for Go. The XLADA API is identical to the Remnawave API, so the client is assembled on the standard net/http library with no external dependency at all — that way you do not depend on someone else's release cycle.

Compatibility

For Remnawave the community maintains a Go SDK generated from the same OpenAPI specification. XLADA responds on the same paths, so it applies here too, but we have not tested it with XLADA officially. The example below depends on no modules at all.

Installation​

You need Go 1.18+ (any is used) and a project module:

mkdir xlada-bot && cd xlada-bot
go mod init example.com/xlada-bot

No external packages are required — only net/http, encoding/json, and context.

Client​

The token is either a JWT from POST /api/auth/login or a long-lived API token from POST /api/tokens. Both are passed in the Authorization: Bearer header. When calling the panel directly, add the X-Forwarded-For and X-Forwarded-Proto: https headers, otherwise the panel's middleware will drop the connection.

package main

import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"time"
)

type Client struct {
baseURL string
token string
http *http.Client
}

func NewClient(baseURL, token string) *Client {
return &Client{
baseURL: baseURL,
token: token,
http: &http.Client{Timeout: 30 * time.Second},
}
}

func (c *Client) do(ctx context.Context, method, path string, body, out any) error {
var payload []byte
if body != nil {
var err error
if payload, err = json.Marshal(body); err != nil {
return err
}
}

req, err := http.NewRequestWithContext(ctx, method, c.baseURL+path, bytes.NewReader(payload))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+c.token)
req.Header.Set("X-Forwarded-For", "127.0.0.1")
req.Header.Set("X-Forwarded-Proto", "https")

res, err := c.http.Do(req)
if err != nil {
return err
}
defer res.Body.Close()

if res.StatusCode >= http.StatusBadRequest {
return fmt.Errorf("xlada: %s %s: %s", method, path, res.Status)
}

if out != nil {
return json.NewDecoder(res.Body).Decode(out)
}
return nil
}

Listing users​

Paginated output is set by the start (offset) and size (page size, 1–1000) query parameters.

type user struct {
ID int `json:"id"`
Username string `json:"username"`
Status string `json:"status"`
ShortUUID string `json:"shortUuid"`
ExpireAt string `json:"expireAt"`
}

type usersResponse struct {
Response struct {
Users []user `json:"users"`
Total int `json:"total"`
} `json:"response"`
}

func listUsers(ctx context.Context, c *Client) error {
var out usersResponse
if err := c.do(ctx, http.MethodGet, "/api/users?start=0&size=25", nil, &out); err != nil {
return err
}

for _, u := range out.Response.Users {
fmt.Printf("%s\t%s\t%s\n", u.Username, u.Status, u.ExpireAt)
}
fmt.Printf("total: %d\n", out.Response.Total)
return nil
}

Creating a user​

POST /api/users accepts username (3–36 characters, a-z, A-Z, 0-9, _, - only) and a required expireAt in ISO 8601 format.

type createUserRequest struct {
Username string `json:"username"`
Status string `json:"status,omitempty"`
ExpireAt string `json:"expireAt"`
TrafficLimitBytes int64 `json:"trafficLimitBytes,omitempty"`
TrafficLimitStrategy string `json:"trafficLimitStrategy,omitempty"`
}

func createUser(ctx context.Context, c *Client) error {
var out struct {
Response struct {
ID int `json:"id"`
ShortUUID string `json:"shortUuid"`
} `json:"response"`
}

err := c.do(ctx, http.MethodPost, "/api/users", createUserRequest{
Username: "john_doe",
Status: "ACTIVE",
ExpireAt: "2026-01-17T15:38:45.065Z",
TrafficLimitStrategy: "NO_RESET",
}, &out)
if err != nil {
return err
}

fmt.Printf("created id=%d, shortUuid=%s\n", out.Response.ID, out.Response.ShortUUID)
return nil
}

Getting a node​

A specific node is requested by uuid; the node list — by GET /api/nodes.

func getNode(ctx context.Context, c *Client, uuid string) error {
var out struct {
Response struct {
Name string `json:"name"`
Address string `json:"address"`
Port *int `json:"port"`
IsConnected bool `json:"isConnected"`
} `json:"response"`
}

if err := c.do(ctx, http.MethodGet, "/api/nodes/"+uuid, nil, &out); err != nil {
return err
}

fmt.Printf("%s (%s) connected=%v\n", out.Response.Name, out.Response.Address, out.Response.IsConnected)
return nil
}
tip

All response structures are described in the specification on the API Specification page. Take the json tags straight from there — panel fields are named in camelCase.

What's next​