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.
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.
| Method | Signature | Purpose |
|---|---|---|
BaseURL | BaseURL() string | The normalized controller base URL the client was built with. |
Version | Version() string | Cached controller version; fetched once on first use. Swallows fetch errors (returns ""). |
VersionContext | VersionContext(ctx) (string, error) | Like Version but honors ctx and surfaces the fetch error. |
Logger | Logger() Logger | The Logger the client was built with. |
Internal | Internal() InternalClient | The Internal (legacy) API surface. Its methods are also reachable directly on c. |
Official | Official() official.Client | The Official OpenAPI surface (controller 10.1.78+). |
Do | Do(ctx, method, apiPath, reqBody, respBody) error | Send an arbitrary request; decode the response into respBody. |
Get | Get(ctx, apiPath, reqBody, respBody) error | GET helper over Do. |
Post | Post(ctx, apiPath, reqBody, respBody) error | POST helper over Do. |
Put | Put(ctx, apiPath, reqBody, respBody) error | PUT helper over Do. |
Patch | Patch(ctx, apiPath, reqBody, respBody) error | PATCH helper over Do. |
Delete | Delete(ctx, apiPath, reqBody, respBody) error | DELETE 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.