A Go client library for the Kaseya VSA X REST API (v3). Provides typed access to devices, device assets, automation (workflows, tasks, scripts), notifications, webhooks, organizations, sites, groups, custom fields, scopes, patch and endpoint-protection policies, environment info, and audit logs, with a fluent OData query builder and automatic pagination.
go get github.com/scjalliance/vsax
Requires Go 1.25+ (uses iter.Seq2 range-over-func iterators).
client, err := vsax.NewClient(vsax.Config{
ServerName: "vsax.example.com", // tenant host (no scheme, no /api/v3)
TokenID: os.Getenv("VSAX_TOKEN_ID"),
TokenSecret: os.Getenv("VSAX_TOKEN_SECRET"),
})
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
for dev, err := range client.Devices.All(ctx, vsax.NewQuery().Top(100)) {
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s %s (group %d)\n", dev.Identifier, dev.Name, dev.GroupID)
}VSA X uses HTTP Basic auth over HTTPS, with a token ID as username and a token secret as password.
client, err := vsax.NewClient(vsax.Config{
ServerName: "vsax.example.com",
TokenID: "TOKEN_ID",
TokenSecret: "TOKEN_SECRET",
})Tokens are generated from the VSA X admin UI. Requests are rejected over plain HTTP.
ServerName is the tenant hostname. The client targets https://<ServerName>/api/v3 automatically.
For testing or non-standard deployments, set BaseURL to override the full URL (scheme + /api/v3 path required):
cfg := vsax.Config{BaseURL: "https://staging.example.com/api/v3", TokenID: "...", TokenSecret: "..."}VSA X uses OData-style list parameters ($top, $skip, $filter, $orderby, $count). The fluent Query type builds them safely:
q := vsax.NewQuery().
Top(100).
Skip(0).
Filter("contains(tolower(Name),'web')").
OrderBy("Name").
Count(true)For common cases, Field generates filter strings with correct quoting:
q := vsax.NewQuery().
Filter(vsax.Field("GroupId").Eq(123))
// Boolean
q := vsax.NewQuery().
Filter(vsax.Field("IsAgentInstalled").Eq(true))
// String contains (OData function)
q := vsax.NewQuery().
Filter(vsax.Field("Name").Contains("web"))
// Combining
q := vsax.NewQuery().
Filter(vsax.And(
vsax.Field("OrganizationId").Eq(5),
vsax.Field("IsOnline").Eq(true),
))Filterable / sortable properties are endpoint-specific — see the VSA X docs per-endpoint.
List endpoints accept $top (limit) and $skip (offset). The server emits Meta.NextQueryLink when total results exceed 5000 items. The All iterator follows it transparently:
for site, err := range client.Sites.All(ctx, nil) {
if err != nil { log.Fatal(err) }
process(site)
}For page-level control:
page, err := client.Devices.List(ctx, vsax.NewQuery().Top(500).Count(true))
fmt.Printf("total: %d\n", page.Meta.TotalCount)
// Range over items in this page + follow NextQueryLink
for d, err := range page.Iter(ctx) {
if err != nil { break }
process(d)
}
// Collect all remaining pages into a slice
all, err := page.Collect(ctx)| Service | Operations |
|---|---|
Devices |
List, All, Get, Publish, Move, Notifications, AllNotifications, Antivirus, CustomFields, AppliedPolicies |
Assets |
List, All, ForDevice (with Include* section selectors) |
Workflows |
List, All, Get, Run, Executions, AllExecutions, Execution, CancelExecutions |
Tasks |
List, All, Get, Run, Execution, ExecutionDevices, AllExecutionDevices, ExecutionScripts, ScriptOutput |
Scripts |
List, All, Get, Run, Executions, AllExecutions, Execution |
Notifications |
List, All, Get, Create, Delete |
NotificationWebhooks |
List, All, Get, Create, Update, Delete, RegenerateSecretKey |
| Service | Operations |
|---|---|
Organizations |
List, All, Get, Create, Update, Delete, CustomFields |
Sites |
List, All, Get, Create, Update, Delete, CustomFields |
Groups |
List, All, Get, Create, Update, Delete, CustomFields, Package(id, pkgType) |
CustomFields |
List, All, Get, Usage, AllUsage, Assign, UpdateAssignment, Unassign |
Scopes |
List, All, Get, Usage, AllUsage |
| Service | Operations |
|---|---|
PatchManagement |
Policy(id), GlobalRules() |
EndpointProtection |
Policy(id) |
Environment |
Get() |
AuditLog |
List, All |
Policy endpoints (PatchManagement.Policy, PatchManagement.GlobalRules, EndpointProtection.Policy) return a PolicyDocument wrapping raw JSON because the schema varies by policy kind. Use doc.As(&myStruct) to decode it.
Environment.Get returns a typed Environment for the common fields (ProductVersion, CustomerId, CustomerName, ServerType, Language) and keeps the License block as json.RawMessage. Call env.DecodeLicense(&myLicense) to decode it into a tenant-specific struct.
GET /assets supports a comma-separated include selector. Use the Include* constants with Query.Include:
q := vsax.NewQuery().Top(100).Include(
vsax.IncludeAvailableUpdates,
vsax.IncludeSecurity,
vsax.IncludeAssetInfo,
vsax.IncludeLocalIPAddresses,
)
page, err := client.Assets.List(ctx, q)The constants for the available sections:
| Constant | Section |
|---|---|
IncludeTags |
Tags |
IncludeUpdates |
Updates |
IncludeAvailableUpdates |
AvailableUpdates |
IncludeSecurity |
Security |
IncludeAssetInfo |
AssetInfo |
IncludeIPAddresses |
IpAddresses |
IncludeLocalIPAddresses |
LocalIpAddresses |
IncludeDisks |
Disks |
IncludeInstalledSoftware |
InstalledSoftware |
IncludeNone |
(no optional sections) |
When include is not set, VSA X defaults to Tags, Updates, AssetInfo, IpAddresses, LocalIpAddresses, Disks, InstalledSoftware.
4xx and 5xx responses return typed errors. Callers can either switch on type or use errors.Is against sentinel errors.
_, err := client.Devices.Get(ctx, "missing-guid")
if err != nil {
switch {
case errors.Is(err, vsax.ErrUnauthorized):
log.Fatal("invalid or expired token")
case errors.Is(err, vsax.ErrForbidden):
log.Printf("permission denied")
case errors.Is(err, vsax.ErrNotFound):
var nfe *vsax.NotFoundError
errors.As(err, &nfe)
log.Printf("%s %q missing", nfe.ResourceType, nfe.ResourceID)
case errors.Is(err, vsax.ErrBadRequest):
log.Printf("validation failed: %v", err)
default:
log.Printf("api error: %v", err)
}
}Transient errors (5xx, network) are retried with exponential backoff up to Config.MaxRetries times (default: 3).
client, err := vsax.NewClient(vsax.Config{
ServerName: "vsax.example.com", // tenant host
TokenID: "TOKEN_ID", // required
TokenSecret: "TOKEN_SECRET", // required
BaseURL: "", // optional: override full base URL (scheme + /api/v3)
HTTPClient: nil, // optional: custom http.Client
MaxRetries: nil, // optional: retry count (nil → 3; set to &zero to disable)
DefaultTimeout: 30 * time.Second, // optional: timeout applied when HTTPClient is nil
UserAgent: "my-app/1.0", // optional: override User-Agent
})cmd/vsa is a read-only CLI that wraps the client library. Useful for exploring a tenant and validating credentials without writing any code.
envwith -f .secrets/.env -- go run ./cmd/vsa whoami
envwith -f .secrets/.env -- go run ./cmd/vsa devices -org 123 -limit 100
envwith -f .secrets/.env -- go run ./cmd/vsa device <GUID>
envwith -f .secrets/.env -- go run ./cmd/vsa find web01
envwith -f .secrets/.env -- go run ./cmd/vsa audit -limit 20
envwith -f .secrets/.env -- go run ./cmd/vsa env
Required environment variables:
| Variable | Purpose |
|---|---|
VSAX_SERVER |
Tenant hostname (e.g. tenant.vsax.net) |
VSAX_TOKEN_ID |
API token ID |
VSAX_TOKEN_SECRET |
API token secret |
Run go run ./cmd/vsa with no args for the full command list.
- VSA X REST API Reference
- API coverage & LLM hints
- AGENTS.md — collaboration notes for LLM agents working on this client
MIT.