AlexxIT/go2rtc · error
request failed with status
Error message
request failed with status %d: %s
What it means
The Ring API returned an HTTP status >= 400 that is not 401 (and not a session-related 404), so the client treats it as a terminal failure. The error message embeds the numeric status and the full response body, which contains Ring's own error explanation. This is a pass-through of a server-side rejection of your request.
Solutions
- Read the body text in the error — it names the exact server-side problem
- If status is 429, add backoff/rate-limiting to your polling loop
- If 400/404, verify the URL, request body shape, and that the target device/endpoint still exists
- If 403, check account permissions/subscription (e.g. Ring Protect) for the requested resource
- If 5xx, retry later — it is a Ring-side outage
Example fix
// before
_, err := client.Request("POST", url, map[string]interface{}{"bad": 1})
// after
payload := map[string]interface{}{"device_id": id, "command": cmd} // match Ring's schema
_, err := client.Request("POST", url, payload)
if err != nil {
var apiErr *ring.StatusError
if errors.As(err, &apiErr) && apiErr.StatusCode == 429 {
time.Sleep(30 * time.Second)
}
} Defensive patterns
Strategy: try-catch
Validate before calling
// validate inputs the Ring API is strict about before calling
if deviceID == "" || strings.Contains(url, " ") {
return errors.New("invalid device id or URL for ring request")
} Type guard
type StatusError struct { StatusCode int; Body string }
func asStatusError(err error) (*StatusError, bool) {
var se *StatusError
if errors.As(err, &se) && se.StatusCode >= 400 { return se, true }
return nil, false
} Try / catch
data, err := client.Request("GET", url, nil)
if se, ok := asStatusError(err); ok {
switch se.StatusCode {
case 429: rateLimitedBackoff()
case 403: return fmt.Errorf("permission/subscription issue: %s", se.Body)
default: return fmt.Errorf("ring api error: %s", se.Body)
}
} Prevention
- Parse the response body in the error before retrying blindly
- Throttle polling to avoid Ring 429 rate limits
- Match request bodies exactly to Ring's current API schema
- Handle 5xx with delayed retries as Ring-side outages
- Check account subscription/permissions for protected endpoints
When it happens
Trigger: Any RingApi.Request() call where Ring replies 400 (bad request payload), 403 (forbidden), 404 (real resource missing), 429 (rate limited), or 5xx — outside the 401/session-refresh retry paths.
Common situations: Malformed request body for a Ring endpoint, calling a deprecated/renamed Ring endpoint, exceeding Ring's rate limits with rapid polling, requesting devices the account no longer owns, or Ring server errors during outages.
Understand the failure class
Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.
Related errors
- res.Status
- session request failed with status
- failed to create auth request
- either email/password or refresh_token is required
- milesone: authentication failed:
AI-assisted analysis of AlexxIT/go2rtc@c245815e75 (2026-09-07).
Data as JSON: /api/errors/de8e569643784976.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/ring/api.go:503
c.sessionExpiry = time.Time{} // Reset session expiry
c.sessionMutex.Unlock()
if attempt == maxRetries {
return nil, fmt.Errorf("session refresh failed after %d retries", maxRetries)
}
if err := c.ensureSession(); err != nil {
return nil, fmt.Errorf("failed to refresh session: %w", err)
}
continue
}
}
}
// Handle other error status codes
if resp.StatusCode >= 400 {
return nil, fmt.Errorf("request failed with status %d: %s", resp.StatusCode, string(responseBody))
}
break
}
return responseBody, nil
}
func (c *RingApi) ensureSession() error {
c.sessionMutex.Lock()
defer c.sessionMutex.Unlock()
// If session is still valid, use it
if c.session != nil && time.Now().Before(c.sessionExpiry) {
return nil
}
// Make sure we have a valid auth tokenView on GitHub (pinned to c245815e75)