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
- Check the embedded status/body: 401/403 means fix the API token/credentials.
- Verify the GrowthBook API URL configuration points at the correct environment.
- If 429, back off and retry with exponential delay / reduce polling frequency.
- For 5xx, retry later or fall back to cached/last-known flag values.
- 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
- Keep the GrowthBook API token valid and rotated before expiry.
- Verify the API URL environment setting (prod vs staging).
- Cache last-known-good flags and fall back when the API errors.
- Add exponential backoff for 429/5xx instead of tight polling.
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
- Whisper API returned
- Bridge register: server returned
- Token exchange failed
- Token exchange failed
- API key creation failed
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)