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
- Generate a new API key in Dozzle Cloud and update the dispatcher configuration (recreating it also resets the 6h breaker)
- Verify the key has no stray whitespace and belongs to the correct account
- Check your cloud subscription/plan status and expiry
- 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
- Validate the API key at startup with a status check before wiring notifications
- Rotate keys via config recreation (which resets the breaker)
- Watch subscription expiry (ExpiresAt)
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
- (await res.json().catch(() =>
- (await res.json().catch(() =>
- Failed to save destination
- dispatchers fetch failed
- cloud dispatcher missing
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)