Kuberwastaken/claurst · error

GrowthBook API returned status

Error message

GrowthBook API returned status {}: {}

What it means

Thrown by `fetch_from_api` (invoked by `fetch_flags_async`) when the GrowthBook HTTP API responds with a non-success status code. The message embeds the numeric status and, when available, the response body text, so the caller can see the server-side reason the flag fetch failed.

Solutions

  1. Check the embedded status/body: 401/403 means fix the API token/credentials.
  2. Verify the GrowthBook API URL configuration points at the correct environment.
  3. If 429, back off and retry with exponential delay / reduce polling frequency.
  4. For 5xx, retry later or fall back to cached/last-known flag values.
  5. Run with the error body from the message to confirm the server-side reason.

Example fix

// resilient flag fetch with fallback
let flags = match fetch_flags_async().await {
    Ok(f) => f,
    Err(e) if e.to_string().contains("status 429") => {
        tokio::time::sleep(Duration::from_secs(30)).await;
        fetch_flags_async().await.unwrap_or_else(|_| cached_flags())
    }
    Err(_) => cached_flags(), // fall back to last known flags
};
Defensive patterns

Strategy: retry

Validate before calling

// preflight: verify credentials/URL with a lightweight request
let status = client.get(&api_url).send().await?.status();
if !status.is_success() { /* fix token or URL before fetching flags */ }

Type guard

fn is_retryable_status(u: u16) -> bool { u == 429 || u >= 500 }

Try / catch

match fetch_flags_async().await {
    Ok(f) => f,
    Err(e) if e.to_string().contains("status 429") || e.to_string().contains("status 5") => {
        backoff_retry(MAX_RETRIES).await
    }
    Err(e) => fall_back_to_cached_flags(e),
}

Prevention

When it happens

Trigger: The reqwest request to the GrowthBook API completes but `status.is_success()` is false — 401/403 for bad or expired API credentials, 404 for a wrong API URL, 429 rate limiting, or 5xx server errors.

Common situations: Rotated or revoked GrowthBook API token; misconfigured GrowthBook API hostname; rate limiting after polling too frequently; GrowthBook service outage returning 502/503.

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 Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/fa04df6c7b29e43c. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/core/src/feature_flags.rs:154

    async fn fetch_from_api(&self) -> Result<CachedFlags> {
        let api_key = std::env::var("GROWTHBOOK_API_KEY").ok();

        let mut builder = self.http_client.get(&self.api_endpoint);

        // Add authorization header if API key is available
        if let Some(key) = api_key {
            builder = builder.header("Authorization", format!("Bearer {}", key));
        }

        let response = builder
            .timeout(Duration::from_secs(10))
            .send()
            .await
            .context("Failed to fetch from GrowthBook API")?;

        let status = response.status();
        if !status.is_success() {
            return Err(anyhow!(
                "GrowthBook API returned status {}: {}",
                status.as_u16(),
                response.text().await.unwrap_or_default()
            ));
        }

        let body = response
            .json::<GrowthBookApiResponse>()
            .await
            .context("Failed to parse GrowthBook API response")?;

        Ok(CachedFlags {
            flags: body
                .features
                .into_iter()
                .map(|f| (f.key.clone(), f))
                .collect(),
            fetched_at: SystemTime::now()

View on GitHub (pinned to b0637c97ec)