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.