xai-org/grok-build · error · acp::Error

Failed to parse billing data: {e}

Error message

Failed to parse billing data: {e}

What it means

The billing endpoint returned HTTP 200, but the body could not be deserialized into `BillingConfigResponse`. The handler logs the parse failure via unified telemetry and returns an ACP internal error 'Failed to parse billing data: {e}'. This is a contract mismatch between what the upstream service sent and the schema the shell expects.

Source

Thrown at crates/codegen/xai-grok-shell/src/extensions/billing.rs:270

            Some(serde_json::json!({
                "status": status,
                "detail": detail,
            })),
        );

        return Err(acp::Error::internal_error().data(format!("Billing service error: {detail}")));
    }

    let mut billing: BillingConfigResponse = credits_resp.json().await.map_err(|e| {
        tracing::error!(error = %e, "billing: failed to parse response");
        xai_grok_telemetry::unified_log::warn(
            "billing: failed to parse response",
            None,
            Some(serde_json::json!({ "error": e.to_string() })),
        );
        acp::Error::internal_error().data(format!("Failed to parse billing data: {e}"))
    })?;

    // Enrich with fields from remote settings.
    let rs = agent.cfg.borrow().remote_settings.clone();
    billing.on_demand_enabled = rs.as_ref().and_then(|rs| rs.on_demand_enabled);
    billing.subscription_tier = rs.as_ref().and_then(|rs| {
        rs.subscription_tier_display
            .clone()
            .or_else(|| rs.subscription_tier.clone())
    });

    // Every prompt / /usage / poll path hits `x.ai/billing`; log the fetched
    // credits snapshot so support can correlate limit UX with real balances.
    xai_grok_telemetry::unified_log::info(
        "billing: fetched credits config",
        None,
        Some(billing_unified_log_ctx(&billing)),
    );

    to_raw_response(&billing)

View on GitHub (pinned to bc7f02eddd)

Solutions

  1. Read `{e}` (also in telemetry data) to identify which field failed to deserialize.
  2. Capture the raw response body (curl the credits endpoint) and compare it against the `BillingConfigResponse` schema.
  3. Upgrade or downgrade the shell so its `BillingConfigResponse` matches the deployed billing API version.
  4. Check for proxies/captive portals returning non-JSON bodies with 200 status.

Example fix

// before: API changed field type, shell expects bool
{ "on_demand_enabled": "true" }
// after: upstream must send the expected type
{ "on_demand_enabled": true }
Defensive patterns

Strategy: type-guard

Validate before calling

// probe the endpoint and check the body is JSON before relying on the parsed result
const body = await fetch(creditsUrl, { headers: auth(token) }).then(r => r.text());
const isJson = body.trimStart().startsWith("{") || body.trimStart().startsWith("[");
if (!isJson) throw new Error("billing endpoint returned non-JSON body");

Type guard

fn is_billing_config(v: &serde_json::Value) -> bool {
    serde_json::from_value::<BillingConfigResponse>(v.clone()).is_ok()
}

Try / catch

try {
  const billing = await acp.request("billing_get");
} catch (e) {
  if (String(e.message).startsWith("Failed to parse billing data")) {
    dumpRawResponseForDiagnostics(); // compare body to BillingConfigResponse schema
  } else { throw e; }
}

Prevention

When it happens

Trigger: `credits_resp.json::<BillingConfigResponse>()` returns `Err(e)` — response body is HTML/JSON from a proxy error page, fields have unexpected types (string vs number), required fields are missing after an API change, or an interceptor returned an empty body.

Common situations: API version drift between shell and billing service (schema change); captive portal or proxy injecting HTML; partial/empty 200 responses during upstream incidents; response envelope renamed (e.g. `on_demand_enabled` type changed).

Understand the failure class

Related errors


AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31). Data as JSON: /api/errors/0e87473d4e2d0f7e. Report an issue: GitHub.