AlexxIT/go2rtc · error

tuya

Error message

tuya: %w

What it means

Client.Dial wraps failures from constructing the Tuya cloud API client (smart API or cloud API) with the "tuya: " prefix. It means authentication or initialization against the Tuya cloud backend failed before any streaming could start.

Solutions

  1. Check the wrapped error (errors.Unwrap) for the precise cloud failure (401 vs DNS vs 4xx)
  2. Re-enter/refresh Tuya account credentials and confirm the device is still bound to the account
  3. Verify the region hostname and API keys (clientId/clientSecret) are correct for your Tuya project
  4. Test the Tuya cloud API separately (curl) to rule out cloud-side outages

Example fix

// before
client, err := tuya.Dial(u, tuya.WithSmartApi(email, password))
// after
client, err := tuya.Dial(u, tuya.WithSmartApi(email, password))
if err != nil {
	var apiErr *tuya.APIError
	if errors.As(err, &apiErr) && apiErr.Code == 401 {
		// refresh credentials / re-login before retrying
	}
	return err
}
Defensive patterns

Strategy: try-catch

Validate before calling

if email == "" || password == "" || deviceId == "" {
	return fmt.Errorf("missing tuya credentials or deviceId")
}

Try / catch

client, err := tuya.Dial(u, opts...)
if err != nil {
	var inner error
	if errors.As(err, &inner) {
		// inner is the raw Tuya cloud error: 401 => refresh creds
	}
	return err
}

Prevention

When it happens

Trigger: Dial with useSmartApi=true and NewTuyaSmartApiClient failing (bad email/password, unreachable host, missing region), or with cloud credentials (uid/clientId/clientSecret/deviceId) rejected by NewTuyaCloudApiClient.

Common situations: Expired or wrong Tuya account credentials; wrong region hostname; revoked or mistyped clientSecret; device removed from the account; Tuya cloud outage or rate limiting.

Related errors


AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07). Data as JSON: /api/errors/734fd77c79ae3544. Report an issue: GitHub.

Appendix: source

Thrown at pkg/tuya/client.go:91

	useSmartApi := deviceId != "" && email != "" && password != ""
	useCloudApi := deviceId != "" && uid != "" && clientId != "" && clientSecret != ""

	if streamResolution == "" || (streamResolution != "hd" && streamResolution != "sd") {
		streamResolution = "hd"
	}

	if !useSmartApi && !useCloudApi {
		return nil, errors.New("tuya: wrong query params")
	}

	client := &Client{
		handlers: make(map[uint32]func(*rtp.Packet)),
	}

	if useSmartApi {
		if client.api, err = NewTuyaSmartApiClient(nil, u.Hostname(), email, password, deviceId); err != nil {
			return nil, fmt.Errorf("tuya: %w", err)
		}
	} else {
		if client.api, err = NewTuyaCloudApiClient(u.Hostname(), uid, deviceId, clientId, clientSecret); err != nil {
			return nil, fmt.Errorf("tuya: %w", err)
		}
	}

	if err := client.api.Init(); err != nil {
		return nil, fmt.Errorf("tuya: %w", err)
	}

	client.streamType = client.api.GetStreamType(streamResolution)
	client.isHEVC = client.api.IsHEVC(client.streamType)

	// Create a new PeerConnection
	conf := pion.Configuration{
		ICEServers:         client.api.GetICEServers(),
		ICETransportPolicy: pion.ICETransportPolicyAll,

View on GitHub (pinned to c245815e75)