Go client library and operator CLI for the Qualithm platform management API. The shared operator
package authenticates with a member API token and backs both the qualithm CLI and the
operator-mcp server, so the two surfaces never
diverge.
operatorclient package — typed methods over the management API, from device provisioning to teams, dashboards, automations, billing and observability.qualithmCLI — verbs over every resource, plus an idempotentapplyfor device-as-code manifests.- Dual output — human-readable tables by default, stable JSON with
--json. - Client-level dry-run —
--dry-runreports the planned mutation without sending it; GETs still execute so reads stay accurate. - Stable exit codes — scriptable status mapping (auth, not-found, conflict, rate-limited, unavailable…).
go install github.com/qualithm/operator-go/cmd/qualithm@latestUse the client library directly:
go get github.com/qualithm/operator-goThe CLI authenticates with a member API token (prefix qmt_). Provide it via --token or the
QUALITHM_API_TOKEN environment variable; point at an environment with --url or
QUALITHM_API_URL (defaults to https://api.qualithm.com).
export QUALITHM_API_TOKEN=qmt_...
# list devices as a table
qualithm device list
# create a time-boxed enrollment code for a space
qualithm enrollment create --space spc_123 --label lab-floor-2
# mint a device credential, machine-readable
qualithm credential mint --device dev_123 --json
# preview a revoke without applying it
qualithm credential revoke --device dev_123 --credential cred_123 --dry-runEvery verb accepts the global flags, before or after its positional arguments:
| Flag | Env | Description |
|---|---|---|
--url |
QUALITHM_API_URL |
management API base URL |
--token |
QUALITHM_API_TOKEN |
member API token (qmt_…) |
--team |
QUALITHM_TEAM |
team to act on (defaults to the token's) |
--json |
— | emit JSON instead of a human table |
--dry-run |
— | report the planned change without applying |
| Resource | Verbs |
|---|---|
authority |
list · create · revoke <id> |
enrollment |
list · create · revoke <id> |
credential |
list · mint · cert · rotate · revoke |
device |
list · get <id> · create · update <id> · delete <id> |
space |
list · get <id> · create · update <id> · delete <id> |
token |
list · create · revoke <id> |
apply |
<manifest.yaml> — idempotent device-as-code reconcile |
version |
print the CLI version |
# devices
qualithm device list --space spc_123 --json
qualithm device get dev_123
qualithm device create --space spc_123 --name gateway-01
# credentials (one-time secrets are printed once, on create)
qualithm credential mint --device dev_123 --label rotate-2027 --expires-at 2027-12-31T00:00:00Z
qualithm credential cert --device dev_123 --csr-file device.csr --expires-days 90
qualithm credential rotate --device dev_123 --credential cred_123 --revoke
# api tokens (create them in the app; the CLI lists and revokes)
qualithm token list
# certificate authorities (bring-your-own PEM)
qualithm authority create --name fleet-ca --kind byo --cert-file ca.pemapply reconciles a fleet manifest, creating any missing devices (matched by name within a space)
and leaving existing ones untouched. Combine with --dry-run to preview.
# fleet.yaml
spaces:
- id: spc_123
devices:
- name: gateway-01
- name: gateway-02qualithm apply fleet.yaml --dry-run # show would-create / exists decisions
qualithm apply fleet.yaml # apply| Code | Meaning |
|---|---|
| 0 | ok (including dry-run) |
| 1 | error |
| 2 | usage |
| 3 | auth (401 / 403) |
| 4 | not found (404) |
| 5 | conflict (409) |
| 6 | rate limited (429) |
| 7 | api (other non-2xx) |
| 8 | unavailable (503) |
403 is not always a missing permission — a paused zone also returns it:
qualithm space create --zone <x> returns exit code 3 with
Zone rejects creation in this environment while the zone is paused. Pick an open zone:
Client.ListZones reports which zones the environment has open.
package main
import (
"context"
"fmt"
"log"
operator "github.com/qualithm/operator-go"
)
func main() {
client, err := operator.New("qmt_...")
if err != nil {
log.Fatal(err)
}
ctx := context.Background()
devices := func(ctx context.Context, cursor string) (operator.Page[operator.Device], error) {
return client.ListDevices(ctx, cursor, operator.WalkLimit)
}
for d, err := range operator.All(ctx, devices) {
if err != nil {
log.Fatal(err)
}
fmt.Println(d.ID, d.Name)
}
}Every List method takes a cursor: empty for the first page, then the previous page's
NextCursor. operator.All walks a list to its end.
Enable a dry-run at construction so mutating calls return a *operator.DryRunError instead of
sending:
client, _ := operator.New("qmt_...", operator.WithDryRun(true))Full API documentation is hosted on pkg.go.dev.
Serve docs locally:
make docsSee the examples/ directory for runnable examples:
| Example | Description |
|---|---|
basic_usage |
Construct a client and list devices. |
go run ./examples/basic_usage- Go 1.26+
make install-toolsThis installs golangci-lint, goimports, govulncheck and gosec into $GOPATH/bin (~/go/bin
by default). Put that directory on your PATH.
make buildmake test # unit tests with race detector
make test-coverage # with coverage reportmake lint
make fmt
make vetmake audit # govulncheck
make gosec # standalone gosec scan
make lint # golangci-lint (includes gosec checks via .golangci.yaml).github/workflows/audit.yaml runs govulncheck and gosec daily.
Go 1.26+.
Apache-2.0