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

  1. Read the body text in the error — it names the exact server-side problem
  2. If status is 429, add backoff/rate-limiting to your polling loop
  3. If 400/404, verify the URL, request body shape, and that the target device/endpoint still exists
  4. If 403, check account permissions/subscription (e.g. Ring Protect) for the requested resource
  5. 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

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


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 token

View on GitHub (pinned to c245815e75)