go-unifi

Client

NewClient and the Client interface — the transport and lifecycle methods plus the Internal and Official surface accessors.

The Client interface is the single entry point to a UniFi controller. You build one with NewClient and hold on to it for the lifetime of your program — it is safe for concurrent use by many goroutines, so share one client rather than creating a client per request.

NewClient

func NewClient(config *ClientConfig) (Client, error)

NewClient validates the *ClientConfig, builds the HTTP transport, and — unless SkipSystemInfo is true — makes one eager round-trip (GetSystemInformation) to confirm the URL is reachable and the API key is valid. That makes a bad key or unreachable host fail fast at construction rather than on your first real call. On any error it returns a nil Client, so a err != nil check fully protects you from a nil-panic.

main.go
package main

import (
	"context"
	"log"

	"github.com/filipowm/go-unifi/v2/unifi"
)

func main() {
	c, err := unifi.NewClient(&unifi.ClientConfig{
		URL:    "https://unifi.example.com",
		APIKey: "your-api-key",
	})
	if err != nil {
		log.Fatalf("create client: %v", err)
	}

	ctx := context.Background()
	if _, err := c.ListNetwork(ctx, "default"); err != nil {
		log.Fatalf("list networks: %v", err)
	}
}

NewClient returns the Client interface, not a concrete struct. That makes it trivial to mock in tests — the package ships a generated ClientMock. See Testing.

The Client interface

Client embeds InternalClient (every legacy resource method — ListNetwork, CreateUser, GetSystemInformation, …) and adds the transport, lifecycle, and surface-accessor methods below.

MethodSignaturePurpose
BaseURLBaseURL() stringThe normalized controller base URL the client was built with.
VersionVersion() stringCached controller version; fetched once on first use. Swallows fetch errors (returns "").
VersionContextVersionContext(ctx) (string, error)Like Version but honors ctx and surfaces the fetch error.
LoggerLogger() LoggerThe Logger the client was built with.
InternalInternal() InternalClientThe Internal (legacy) API surface. Its methods are also reachable directly on c.
OfficialOfficial() official.ClientThe Official OpenAPI surface (controller 10.1.78+).
DoDo(ctx, method, apiPath, reqBody, respBody) errorSend an arbitrary request; decode the response into respBody.
GetGet(ctx, apiPath, reqBody, respBody) errorGET helper over Do.
PostPost(ctx, apiPath, reqBody, respBody) errorPOST helper over Do.
PutPut(ctx, apiPath, reqBody, respBody) errorPUT helper over Do.
PatchPatch(ctx, apiPath, reqBody, respBody) errorPATCH helper over Do.
DeleteDelete(ctx, apiPath, reqBody, respBody) errorDELETE helper over Do.

For the full embedded resource surface, see the Client godoc.

Lifecycle methods

func exampleLifecycle(ctx context.Context, c unifi.Client) error {
	_ = c.BaseURL() // e.g. "https://unifi.example.com"

	// Version() swallows fetch errors (returns ""); VersionContext surfaces them.
	_ = c.Version()
	v, err := c.VersionContext(ctx)
	if err != nil {
		return fmt.Errorf("controller version: %w", err)
	}

	// The logger the client was built with.
	c.Logger().Debugf("controller %s is running %s", c.BaseURL(), v)
	return nil
}

The two API surfaces

Internal() and Official() return the Internal and Official clients. In 2.0.0 the Internal surface is the default: every resource method is reachable directly on c, so c.ListNetwork(...) and c.Internal().ListNetwork(...) are equivalent.

func exampleSurfaces(ctx context.Context, c unifi.Client) error {
	// Internal (legacy) resource methods — also callable directly on c.
	internal := c.Internal()
	if _, err := internal.ListNetwork(ctx, "default"); err != nil {
		return fmt.Errorf("list networks: %w", err)
	}

	// Official OpenAPI surface (controller 10.1.78+); sites are addressed by UUID.
	siteID, err := c.Official().Sites().ResolveID(ctx, "default")
	if err != nil {
		return fmt.Errorf("resolve site id: %w", err)
	}
	fmt.Println("official site id:", siteID)
	return nil
}

See Choosing a surface for how to decide between them.

Raw transport methods

When no typed method exists for an endpoint, drop down to Do/Get/Post/Put/Patch/Delete. Each takes an apiPath (resolved against the controller base URL), an optional request body, and a pointer to decode the response into.

func exampleRawRequest(ctx context.Context, c unifi.Client) error {
	var resp struct {
		Data []struct {
			Name string `json:"name"`
		} `json:"data"`
	}
	// apiPath is resolved against the controller base URL.
	if err := c.Get(ctx, "s/default/stat/health", nil, &resp); err != nil {
		return fmt.Errorf("raw GET: %w", err)
	}
	return nil
}

See Raw HTTP access for the full treatment, including file uploads.

Interceptors are registered up front through ClientConfig.Interceptors — there is no AddInterceptor on the Client interface. See Interceptors.

See also

On this page