go-unifi

Using the Official API

Reach the Official UniFi OpenAPI through c.Official() — fluent per-group accessors, uniform method shapes, and site UUIDs.

c.Official() returns the Official UniFi OpenAPI (integration/v1) surface: a fluent client with one accessor per resource group. This guide covers the accessors, the uniform method shapes, and how site IDs work. If you haven't yet, read Choosing a surface first — the Official surface is gated and requires controller 10.1.78+.

Fluent per-group accessors

Each accessor on c.Official() returns an independently usable (and mockable) per-group interface, derived from the OpenAPI tags:

AccessorWhat it exposes
Networks()networks / VLANs
Firewall()zone-based policies, zones, rule ordering (incl. PatchPolicy)
Devices()adopted devices, pending adoptions, device/port actions
Clients()connected clients
ACLs()ACL rules and ordering
DNSPolicies()DNS policies
TrafficMatchingLists()traffic-matching lists
WifiBroadcasts()Wi-Fi broadcasts
Hotspot()hotspot vouchers (ListVouchersPage, CreateVouchers, …)
Supporting()read-only catalogs: countries, RADIUS profiles, WANs, DPI, …
Sites()site listing and name → UUID resolution
Info()controller application info

There is no top-level Vouchers() accessor — vouchers live under c.Official().Hotspot(). Likewise, firewall resources (including PatchPolicy) live under c.Official().Firewall().

Uniform method shapes

Methods are generated in a uniform shape per resource, so once you know one group you know them all:

  • Read oneGet…(ctx, siteId, id) returns the single-item …Details.
  • List — two methods, so draining is explicit and never accidental:
    • List…Page(ctx, siteId, *official.ListOptions) returns one bounded official.Page[T].
    • List…All(ctx, siteId, filter) returns a lazy iter.Seq2[T, error] you can range over.
  • WriteCreate… / Update… take the …CreateOrUpdate body; some groups add Patch….
  • DeleteDelete…(ctx, siteId, id).

Pagination and filtering get their own guide: see Pagination & filtering.

Site IDs

Every site-scoped resource method takes a siteId uuid.UUID (not the legacy site-name string). The two global accessors are the exception: Sites() and Info() take no siteId — and Sites() is how you obtain one in the first place. Get the UUID from a familiar name with Sites().ResolveID, which caches the lookup:

official.go
siteID, err := c.Official().Sites().ResolveID(ctx, "default")
if err != nil {
	log.Fatal(err)
}

networks, err := c.Official().Networks().ListPage(ctx, siteID, nil)
if err != nil {
	log.Fatal(err)
}
for _, n := range networks.Items {
	fmt.Printf("%s vlan=%d\n", n.Name, n.VlanId)
}

vouchers, err := c.Official().Hotspot().ListVouchersPage(ctx, siteID, nil)
if err != nil {
	log.Fatal(err)
}
for _, v := range vouchers.Items {
	fmt.Println("voucher:", v.Code)
}

Already have a UUID string? Parse it with uuid.Parse (from github.com/google/uuid) instead of resolving a name:

parse.go
siteID, err := uuid.Parse("0fc8a3b2-3c1e-4f6a-9b2d-1a2b3c4d5e6f")
if err != nil {
	log.Fatal(err)
}
page, err := c.Official().Networks().ListPage(ctx, siteID, nil)

ResolveID returns official.ErrSiteNotFound when no site matches the name. The first resolve drains the full site list and caches every name → UUID mapping, so repeated lookups are free.

Next steps

For exhaustive types and method signatures, see the official package on pkg.go.dev.

On this page