netbirdio/netbird · error

one of SetupKey, JWTToken, or PrivateKey must be provided

Error message

one of SetupKey, JWTToken, or PrivateKey must be provided

What it means

embed.Options.validateCredentials requires exactly one registration credential for the embedded client (client/embed): SetupKey, JWTToken, or PrivateKey. This is the zero-credential case — the counter never incremented, so New() aborts before creating the client. The embedded client has no interactive SSO fallback, hence the hard requirement.

Source

Thrown at client/embed/embed.go:140

	// only; ignored by Client.SetPerformance.
	MaxBatchSize *uint32
}

// validateCredentials checks that exactly one credential type is provided
func (opts *Options) validateCredentials() error {
	credentialsProvided := 0
	if opts.SetupKey != "" {
		credentialsProvided++
	}
	if opts.JWTToken != "" {
		credentialsProvided++
	}
	if opts.PrivateKey != "" {
		credentialsProvided++
	}

	if credentialsProvided == 0 {
		return fmt.Errorf("one of SetupKey, JWTToken, or PrivateKey must be provided")
	}
	if credentialsProvided > 1 {
		return fmt.Errorf("only one of SetupKey, JWTToken, or PrivateKey can be specified")
	}

	return nil
}

// New creates a new netbird embedded client.
func New(opts Options) (*Client, error) {
	if err := opts.validateCredentials(); err != nil {
		return nil, err
	}

	if opts.MTU != nil {
		if err := iface.ValidateMTU(*opts.MTU); err != nil {
			return nil, fmt.Errorf("invalid MTU: %w", err)
		}

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Provide a setup key: opts.SetupKey = "AAAA..."
  2. Or provide a JWT token obtained out-of-band: opts.JWTToken = token
  3. Or provide the peer private key for re-registration: opts.PrivateKey = key

Example fix

// before
client, err := embed.New(embed.Options{ManagementURL: "https://mgr"})  // err: one of SetupKey, JWTToken, or PrivateKey must be provided
// after
client, err := embed.New(embed.Options{
	ManagementURL: "https://mgr",
	SetupKey:      os.Getenv("NETBIRD_SETUP_KEY"),
})
Defensive patterns

Strategy: type-guard

Type guard

func hasExactlyOneCredential(o embed.Options) error {
	n := 0
	for _, c := range []string{o.SetupKey, o.JWTToken, o.PrivateKey} {
		if c != "" {
			n++
		}
	}
	if n != 1 {
		return fmt.Errorf("got %d credentials, want exactly 1", n)
	}
	return nil
}

Try / catch

if _, err := embed.New(opts); err != nil {
	if strings.Contains(err.Error(), "must be provided") {
		// credential wiring bug, not transient: fetch a setup key and rebuild opts
	}
}

Prevention

When it happens

Trigger: Calling embed.New(embed.Options{...}) without setting any of the three credential fields, e.g. intending interactive login which the embedded build does not support.

Common situations: Porting CLI-style usage (where `netbird up` can prompt for SSO) into the embedded library, or forgetting to wire the credential from the environment into Options.

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/035f8918c8e950fb. Report an issue: GitHub.