{"record":{"id":"e74704962bf18b02","repo":"googleworkspace/cli","slug":"failed-to-fetch-discovery-document-for-service","errorCode":null,"errorMessage":"Failed to fetch Discovery Document for {service}/{version}: HTTP {} (tried both standard and $discovery URLs)","messagePattern":"Failed to fetch Discovery Document for (.+?)/(.+?): HTTP (.+?) \\(tried both standard and \\$discovery URLs\\)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/google-workspace/src/discovery.rs","lineNumber":240,"sourceCode":"        crate::validate::encode_path_segment(version),\n    );\n\n    tracing::debug!(service = %service, version = %version, \"Fetching discovery document\");\n    let client = crate::client::build_client()?;\n    let resp = client.get(&url).send().await?;\n\n    let body = if resp.status().is_success() {\n        resp.text().await?\n    } else {\n        // Try the $discovery/rest URL pattern used by newer APIs (Forms, Keep, Meet, etc.)\n        let alt_url = format!(\"https://{service}.googleapis.com/$discovery/rest\");\n        let alt_resp = client\n            .get(&alt_url)\n            .query(&[(\"version\", version)])\n            .send()\n            .await?;\n        if !alt_resp.status().is_success() {\n            anyhow::bail!(\n                \"Failed to fetch Discovery Document for {service}/{version}: HTTP {} (tried both standard and $discovery URLs)\",\n                alt_resp.status()\n            );\n        }\n        alt_resp.text().await?\n    };\n\n    // Write to cache\n    if let Some(dir) = cache_dir {\n        let cache_file = dir.join(format!(\"{service}_{version}.json\"));\n        if let Err(e) = tokio::fs::write(&cache_file, &body).await {\n            tracing::warn!(error = %e, \"Failed to write discovery cache\");\n        }\n    }\n\n    let doc: RestDescription = serde_json::from_str(&body)?;\n    Ok(doc)\n}","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/googleworkspace/cli/blob/a3768d0e82ad83cca2da97724e46bea4ff0e6dbd/crates/google-workspace/src/discovery.rs#L222-L258","documentation":"This error is thrown by fetch_discovery_document() after BOTH attempts to download a Google Discovery Document returned a non-2xx HTTP status: first the standard registry URL https://www.googleapis.com/discovery/v1/apis/{service}/{version}/rest, then the fallback https://{service}.googleapis.com/$discovery/rest?version={version} used by newer APIs (Forms, Keep, Meet). The status code in the message is from the second ($discovery) attempt. It means the CLI resolved a service alias but Google's endpoints rejected the request, so no command tree can be built for that service.","triggerScenarios":"Calling gws with a service whose alias exists in services.rs but whose version string is wrong or retired (404); using a service that is registered in neither the Discovery registry nor the $discovery endpoint (404); a corporate proxy or captive portal returning 403/407 for googleapis.com; Google API deprecating a version (410 gone); regional firewall returning 403. Note this fires only on HTTP status failures — a connection failure surfaces as a reqwest error from .send() instead.","commonSituations":"Typo'd or outdated version in crates/google-workspace/src/services.rs after adding a new service alias; running in CI behind an egress proxy that blocks googleapis.com; using a stale service mapping after Google renamed/retired an API; air-gapped or DNS-hijacked networks where the proxy answers with an error page.","solutions":["Run with GOOGLE_WORKSPACE_CLI_LOG=gws=debug to see the exact URLs and statuses tried, then curl both URLs (https://www.googleapis.com/discovery/v1/apis/{service}/{version}/rest and https://{service}.googleapis.com/$discovery/rest?version={version}) to see which status comes back","Check crates/google-workspace/src/services.rs: confirm the alias maps to the exact API name and a currently-published version (compare with the service's Google API docs page)","If a proxy/firewall is involved, allow-list www.googleapis.com and *.googleapis.com, or set HTTPS_PROXY to a proxy that permits those hosts","If the service genuinely serves only the $discovery pattern, verify the host format https://{service}.googleapis.com matches the service's canonical hostname (some services differ, e.g. different API name vs hostname)","Clear the discovery cache only if you suspect corruption: the 24h cache in the configured cache dir is written solely from successful responses, so a stale-bad cache is unlikely — prefer fixing the URL/mapping"],"exampleFix":"// crates/google-workspace/src/services.rs — before\n(\"forms\", \"forms\", \"v2\"), // 404: only v1 exists\n\n// after\n(\"forms\", \"forms\", \"v1\"),","handlingStrategy":"retry","validationCode":"use google_workspace::services;\n\nlet alias = \"forms\";\n// Fail fast if the alias is unknown before touching the network\nlet (api, version) = services::resolve(alias) // or your alias map lookup\n    .unwrap_or_else(|| panic!(\"unknown service alias {alias}\"));\n// Optional reachability pre-flight\n// curl-equivalent: HEAD https://www.googleapis.com/discovery/v1/apis/{api}/{version}/rest","typeGuard":null,"tryCatchPattern":"match google_workspace::discovery::fetch_discovery_document(api, version, cache).await {\n    Ok(doc) => { /* build commands */ }\n    Err(e) if e.to_string().contains(\"Failed to fetch Discovery Document\") => {\n        eprintln!(\"service {api}/{version} unavailable: check alias/version in services.rs and network egress to googleapis.com\");\n        std::process::exit(2);\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Keep the alias map in crates/google-workspace/src/services.rs in sync with published API versions; add a unit test asserting each registered alias resolves to a URL that returned 200 in a recorded fixture","Allow-list www.googleapis.com and *.googleapis.com in corporate proxies and set HTTPS_PROXY so the retrying client in client.rs can use it","Run with GOOGLE_WORKSPACE_CLI_LOG=debug when adding a new service to see which of the two URL patterns the API actually serves"],"tags":["network","discovery","http","google-api","cli"],"backgroundTag":"discovery-document-fetch-failed","analyzedSha":"a3768d0e82ad83cca2da97724e46bea4ff0e6dbd","analyzedAt":"2026-08-16T19:51:46.516Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}