amir20/dozzle · error

cloud returned status code

Error message

cloud returned status code %d: %s

What it means

Dozzle Cloud rejected the request with HTTP 401 Unauthorized or 403 Forbidden. The dispatcher reads up to 1MB of the response body, sets a 6-hour backoff (unauthorizedRetryAfter) because retrying cannot help until the key is fixed, and returns this error including the status code and body text.

Solutions

  1. Generate a new API key in Dozzle Cloud and update the dispatcher configuration (recreating it also resets the 6h breaker)
  2. Verify the key has no stray whitespace and belongs to the correct account
  3. Check your cloud subscription/plan status and expiry
  4. If the key was fixed out-of-band, call ResetBreaker() after a successful status check instead of waiting 6 hours

Example fix

// before
dispatcher, _ := NewCloudDispatcher("cloud", oldKey, "", nil)
// after
dispatcher, _ := NewCloudDispatcher("cloud", newlyGeneratedKey, "", nil)
Defensive patterns

Strategy: validation

Validate before calling

if apiKey == nil || strings.TrimSpace(apiKey) == "" {
    return errors.New("cloud API key missing")
}

Try / catch

if err := d.Send(ctx, n); err != nil && strings.Contains(err.Error(), "status code 40") {
    // stop retrying; prompt user to regenerate the API key
}

Prevention

When it happens

Trigger: CloudDispatcher.Send received 401/403 from cloud, typically because the configured API key is invalid, revoked, or expired (ExpiresAt passed), or the account lacks the plan/permission for this endpoint.

Common situations: Rotated or deleted API key still configured in the dispatcher; expired subscription; key copied with whitespace or from the wrong account; cloud plan downgraded removing notification access.

Related errors


AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07). Data as JSON: /api/errors/d432f7114f703e11. Report an issue: GitHub.

Appendix: source

Thrown at internal/notification/dispatcher/cloud.go:122

		}
		c.blockedUntil.Store(time.Now().Add(retryAfter).UnixNano())
		log.Warn().
			Str("cloud", c.Name).
			Dur("retry_after", retryAfter).
			Msg("rate limited by cloud, circuit breaker tripped")
		return fmt.Errorf("cloud rate limited, backing off for %s", retryAfter)
	}

	if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden {
		limitedReader := io.LimitReader(resp.Body, 1024*1024)
		responseBody, _ := io.ReadAll(limitedReader)
		c.blockedUntil.Store(time.Now().Add(unauthorizedRetryAfter).UnixNano())
		log.Warn().
			Str("cloud", c.Name).
			Int("status_code", resp.StatusCode).
			Dur("retry_after", unauthorizedRetryAfter).
			Msg("cloud rejected API key, circuit breaker tripped")
		return fmt.Errorf("cloud returned status code %d: %s", resp.StatusCode, string(responseBody))
	}

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		limitedReader := io.LimitReader(resp.Body, 1024*1024)
		responseBody, _ := io.ReadAll(limitedReader)
		log.Debug().
			Str("cloud", c.Name).
			Str("url", c.URL).
			Int("status_code", resp.StatusCode).
			Str("payload", string(payload)).
			Str("response_body", string(responseBody)).
			Msg("cloud returned non-success status code")
		return fmt.Errorf("cloud returned status code %d: %s", resp.StatusCode, string(responseBody))
	}

	return nil
}

View on GitHub (pinned to d9463cbe21)