Skip to content

API Module

The api/ module is a standalone Go library for communicating with the z13ctl daemon from external tools — GUI frontends, Decky plugins, shell integrations, or anything else that wants to control z13ctl programmatically.

Import

import "github.com/dahui/z13ctl/api"

The module is deliberately stdlib-only (no third-party dependencies) so that integrations can pull it in without inheriting the CLI's dependency tree.

It is a separate Go module at ./api with its own go.mod. If you are working on both the main binary and the API library simultaneously, create a go.work file:

go work init . ./api

Connection model

All Send* functions open a fresh Unix socket connection to the daemon, send one JSON request, read one JSON response, and close the connection. This is intentionally simple and stateless.

If the daemon is not running (connection refused), every Send* function returns (false, nil) — the first return value (handled bool) signals whether the daemon was reached. Callers can use this to decide whether to fall back to direct hardware access.

handled, err := api.SendApply("", "FF0000", "000000", "static", "normal", 3)
if !handled {
    // daemon not running; do your own HID access here
}

Subscribe follows the same pattern but holds the connection open to receive a stream of events.


Socket path

path := api.SocketPath()
// $XDG_RUNTIME_DIR/z13ctl/z13ctl.sock  (or /tmp/z13ctl/z13ctl.sock)

Examples

Apply lighting:

// Static cyan at full brightness on all devices
handled, err := api.SendApply("", "00FFFF", "000000", "static", "normal", 3)

// Breathe between red and blue on the keyboard only
handled, err := api.SendApply("keyboard", "FF0000", "0000FF", "breathe", "slow", 3)

System settings:

// Battery limit
handled, limit, err := api.SendBatteryLimitGet()
handled, err       := api.SendBatteryLimitSet(80)

// Performance profile
handled, profile, err := api.SendProfileGet()
handled, err          := api.SendProfileSet("performance")

// Boot sound and panel overdrive
handled, err := api.SendBootSoundSet(0)
handled, err := api.SendPanelOverdriveSet(1)

// Fan curves (applied to both fans simultaneously)
handled, value, err := api.SendFanCurveGet()
handled, err         := api.SendFanCurveSet("48:2,53:22,57:30,60:43,63:56,65:68,70:89,76:102")
handled, err         := api.SendFanCurveReset()

// TDP (PPT power limits)
handled, value, err := api.SendTdpGet()
handled, err         := api.SendTdpSet("50", "", "", "", false)  // all PPTs to 50W
handled, err         := api.SendTdpReset()

// Undervolt (Curve Optimizer — requires ryzen_smu kernel module)
handled, value, err := api.SendUndervoltGet()
handled, err         := api.SendUndervoltSet("-20")  // CPU CO -20
handled, err         := api.SendUndervoltReset()

Full state snapshot (for GUI initialization):

handled, state, err := api.SendGetState()
if handled && err == nil {
    fmt.Println("lighting mode:", state.Lighting.Mode)
    fmt.Println("profile:", state.Profile)
    fmt.Println("battery limit:", state.Battery)
    fmt.Println("fan curve:", state.FanCurve)
    fmt.Println("tdp:", state.TDP)
    fmt.Println("undervolt:", state.Undervolt)
    fmt.Println("undervolt available:", state.UndervoltAvailable)
    fmt.Println("APU temp:", state.Temperature, "°C")
    fmt.Println("fan RPM:", state.FanRPM)
}

Subscribe to Armoury Crate button events:

ch, cancel, err := api.Subscribe([]string{"gui-toggle"})
if ch == nil {
    // daemon not running
}
defer cancel()
for event := range ch {
    fmt.Println("received:", event)
}

Full API reference

See the API Reference page for full documentation of all exported types and functions.