nikivdev/code · error

API error {}: {}

Error message

API error {}: {}

What it means

This error is raised by `fetch_project_sealed_env` (and sibling cloud fetches) in src/env.rs when the remote API responds with any non-2xx, non-401, non-404 status. The library embeds the HTTP status code and the raw response body into the message so the developer can see exactly what the server rejected and why.

Source

Thrown at src/env.rs:1456

    let resp = client
        .post(url)
        .header("Authorization", format!("Bearer {}", token))
        .json(&body)
        .send()
        .context("failed to connect to cloud")?;

    if resp.status() == 404 {
        return Ok(identity);
    }

    if resp.status() == 401 {
        bail!("Unauthorized. Check your token with `f env login`.");
    }

    if !resp.status().is_success() {
        let status = resp.status();
        let body = resp.text().unwrap_or_default();
        bail!("API error {}: {}", status, body);
    }

    Ok(identity)
}

fn fetch_project_member_sealer_ids(
    project_name: &str,
    self_sealer_id: &str,
    api_url: &str,
    token: &str,
    client: &reqwest::blocking::Client,
) -> Result<Vec<String>> {
    let url = Url::parse(&format!(
        "{}/api/env/projects/{}/sealers",
        api_url, project_name
    ))?;
    let resp = client
        .get(url)

View on GitHub (pinned to a747e741ae)

Solutions

  1. Read the status and body in the message: 5xx/429 usually mean retry later with backoff; 403 means fix project membership/permissions
  2. Run `f env login` to obtain a fresh token, then retry
  3. Check the response body for a server-side error message pointing at the failing request field
  4. Verify network/proxy settings if the body looks like an HTML error page from an intermediary

Example fix

// before: no handling of 429/5xx
let identity = fetch_sealed_env(...)?;
// after: retry transient statuses
match fetch_sealed_env(...) {
    Err(e) if is_retryable(&e) => retry_with_backoff(3, || fetch_sealed_env(...)),
    other => other,
}
Defensive patterns

Strategy: retry

Validate before calling

// Rust: cheap preflight to catch auth/permission issues before heavy work
pub fn preflight(client: &Client, token: &str) -> Result<(), Error> {
    let r = client.get("/api/me").bearer_auth(token).send()?;
    match r.status() {
        s if s.is_success() => Ok(()),
        s => Err(Error::msg(format!("preflight failed: {}", s))),
    }
}

Type guard

pub fn is_api_error(e: &Error) -> bool {
    e.to_string().starts_with("API error ")
}
pub fn api_status(e: &Error) -> Option<u16> {
    e.to_string().splitn(3, ' ').nth(2)?.split(':').next()?.parse().ok()
}

Try / catch

match api_status(&err) {
    Some(429) | Some(500..=599) => retry_with_backoff(3, op),
    Some(403) => eprintln!("permission denied: request project access"),
    _ => return Err(err),
}

Prevention

When it happens

Trigger: A `reqwest` GET/POST to the cloud env endpoint returns a status that is not 2xx, not 401, and not 404 — e.g. 403 Forbidden (token valid but lacking project access), 429 Too Many Requests, or 500/502/503 from the server.

Common situations: Expired-but-not-revoked token hitting a project the account is no longer a member of (403); server-side outage or rate limiting (429/5xx); API version mismatch after a server upgrade returning 400 Bad Request; proxy or corporate firewall altering requests.

Related errors


AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01). Data as JSON: /api/errors/88a231e2b5657790. Report an issue: GitHub.