go-unifi

Errors

The go-unifi error model — ServerError, ValidationError, and the Err sentinels you match with errors.Is and errors.As.

go-unifi returns three kinds of error: sentinels you match with errors.Is, a structured ServerError for controller-side failures, and a client-side ValidationError for request bodies rejected before they are sent. All returned errors are wrapped with %w, so errors.Is / errors.As see through the wrapping. For the task-oriented guide see Error handling.

Sentinels

SentinelMeaning
ErrNotFoundThe resource does not exist (a real HTTP 404, or a single-resource Get that returned empty data). List methods return an empty slice and nil error, not ErrNotFound.
ErrOldStyleUnsupportedNewClient targeted a classic (old-style) controller. API-key auth needs UniFi Network 9.0.114+.
ErrOfficialAPIUnavailableThe Official API cannot run against this controller — an old-style (classic) controller, a failed GET /v1/info probe (a rejected API key surfaces here), or a version below 10.1.78.
ErrOfficialAPIDisabledThe Official API was opted out via ClientConfig.DisableOfficialAPI.

Match them with errors.Is:

func exampleNotFound(ctx context.Context, c unifi.Client) {
	_, err := c.GetNetwork(ctx, "default", "nonexistent-id")
	switch {
	case err == nil:
		// found
	case errors.Is(err, unifi.ErrNotFound):
		fmt.Println("network not found")
	default:
		fmt.Println("lookup failed:", err)
	}
}

ErrNotFound covers both a genuine HTTP 404 and the "empty result" case: a single-resource Get whose lookup came back empty. A list endpoint that returns no items is not ErrNotFound — it yields an empty slice and a nil error. A *ServerError with a 404 status also satisfies errors.Is(err, unifi.ErrNotFound).

These surface at different points: ErrOldStyleUnsupported comes from NewClient at construction, while the two Official-API sentinels come from the first Official() operation — the capability gate probes lazily, so they never surface from NewClient. Classify whichever error you are holding:

func exampleAPISentinels(err error) {
	switch {
	case errors.Is(err, unifi.ErrOldStyleUnsupported):
		fmt.Println("classic controller; upgrade to 9.0.114+ for API-key auth")
	case errors.Is(err, unifi.ErrOfficialAPIUnavailable):
		fmt.Println("controller too old for the Official API (needs 10.1.78+)")
	case errors.Is(err, unifi.ErrOfficialAPIDisabled):
		fmt.Println("Official API disabled via DisableOfficialAPI")
	}
}

ServerError

A controller that returns a non-2xx response (or a soft rc:"error" envelope on a 200) yields a *ServerError. It carries the request context and any structured validation details the controller reported.

Prop

Type

ServerError implements error and an Is method so a 404 maps to ErrNotFound. Each entry in Details is a ServerErrorDetails:

TypeFields
ServerErrorDetailsMessage string, ValidationError ServerValidationError
ServerValidationErrorField string, Pattern string

Meta (RC string, Message string) is a separate type — the raw v1 { "meta": { "rc", "msg" } } envelope the handler parses — not a field of Details.

Reach the structured error with errors.As:

func exampleServerError(ctx context.Context, c unifi.Client) {
	_, err := c.CreateNetwork(ctx, "default", &unifi.Network{})
	var serr *unifi.ServerError
	if errors.As(err, &serr) {
		fmt.Printf("status=%d code=%s msg=%s\n", serr.StatusCode, serr.ErrorCode, serr.Message)
		for _, d := range serr.Details {
			fmt.Printf("  detail=%s field=%s pattern=%s\n",
				d.Message, d.ValidationError.Field, d.ValidationError.Pattern)
		}
	}
}

See the canonical definitions on ServerError.

ValidationError

When ValidationMode is HardValidation, a request body that fails the built-in struct validation is rejected before any HTTP call with a *ValidationError. It carries the underlying validator error in Root (reachable via Unwrap) and a per-field map in Messages.

TypeFields
ValidationErrorRoot error, Messages map[string]string
func exampleValidationError(err error) {
	var verr *unifi.ValidationError
	if errors.As(err, &verr) {
		for field, msg := range verr.Messages {
			fmt.Printf("%s: %s\n", field, msg)
		}
	}
}

ValidationError is a client-side error (the request never reached the controller); ServerError is the controller's own rejection. Under the default SoftValidation mode, validation failures are only logged — you get a ServerError instead if the controller rejects the body.

See also

On this page