{"record":{"id":"fc7a56f8bc2b451a","repo":"googleworkspace/cli","slug":"5-fc7a56","errorCode":"5","errorMessage":"Model Armor API returned status {status}: {resp_text}","messagePattern":"Model Armor API returned status (.+?): (.+?)","errorType":"http","errorClass":"GwsError","httpStatus":null,"severity":"error","filePath":"crates/google-workspace-cli/src/helpers/modelarmor.rs","lineNumber":274,"sourceCode":"\n    let client = crate::client::build_client()?;\n    let resp = client\n        .post(&url)\n        .header(\"Authorization\", format!(\"Bearer {token}\"))\n        .header(\"Content-Type\", \"application/json\")\n        .body(body)\n        .send()\n        .await\n        .context(\"Model Armor request failed\")?;\n\n    let status = resp.status();\n    let resp_text = resp\n        .text()\n        .await\n        .context(\"Failed to read Model Armor response\")?;\n\n    if !status.is_success() {\n        return Err(GwsError::Other(anyhow::anyhow!(\n            \"Model Armor API returned status {status}: {resp_text}\"\n        )));\n    }\n\n    parse_sanitize_response(&resp_text)\n}\n\n/// Make a POST request to Model Armor's regional API endpoint.\nasync fn model_armor_post(url: &str, body: &str) -> Result<(), GwsError> {\n    let token = auth::get_token(&[CLOUD_PLATFORM_SCOPE])\n        .await\n        .context(\"Failed to get auth token\")?;\n\n    let client = crate::client::build_client()?;\n    let resp = client\n        .post(url)\n        .header(\"Authorization\", format!(\"Bearer {token}\"))\n        .header(\"Content-Type\", \"application/json\")","sourceCodeStart":256,"sourceCodeEnd":292,"githubUrl":"https://github.com/googleworkspace/cli/blob/a3768d0e82ad83cca2da97724e46bea4ff0e6dbd/crates/google-workspace-cli/src/helpers/modelarmor.rs#L256-L292","documentation":"Returned by `sanitize_text()` when the Model Armor regional endpoint (`modelarmor.{location}.rep.googleapis.com/.../templates/{template}:sanitizeUserPrompt` or `:sanitizeModelResponse`) answers with a non-2xx HTTP status. The raw status code and response body are embedded verbatim, so the Google error JSON (e.g. `PERMISSION_DENIED`, `NOT_FOUND`, `INVALID_ARGUMENT`) is the actual diagnostic.","triggerScenarios":"`gws modelarmor +sanitize-prompt --template projects/P/locations/L/templates/T ...` with a template that does not exist (404); the Model Armor API not enabled in project P (403); a typo'd location like `us-central` or `global` (404); a template name missing the `projects/.../templates/...` segments (400); an expired OAuth token (401).","commonSituations":"Sanitizing before sending via Gmail helpers with `--sanitize`; template created in one project/location but referenced with another; fresh GCP project where `modelarmor.googleapis.com` was never enabled; using a service account/ADC without the Model Armor User role.","solutions":["Read the embedded body: 404 NOT_FOUND means the template path is wrong — verify with `gws modelarmor projects locations templates list` (Discovery command).","403 PERMISSION_DENIED: enable the Model Armor API in the template's project and grant the caller `modelarmor.user` (or Editor) on it.","Confirm the template string has all four segments: projects/PROJECT/locations/LOCATION/templates/TEMPLATE with a valid region like us-central1.","401/invalid token: re-run `gws auth login` or refresh ADC (`gcloud auth application-default login`)."],"exampleFix":"# before\ngws modelarmor +sanitize-prompt --template my-tmpl --text 'hello'\n# -> Model Armor API returned status 404 Not Found: ...NOT_FOUND...\n\n# after — full resource name with project + location\ngws modelarmor +sanitize-prompt \\\n  --template projects/my-proj/locations/us-central1/templates/my-tmpl \\\n  --text 'hello'","handlingStrategy":"try-catch","validationCode":"// Validate the template resource name before calling sanitize_text\nfn valid_template_name(t: &str) -> bool {\n    let parts: Vec<&str> = t.split('/').collect();\n    parts.len() == 6\n        && parts[0] == \"projects\" && !parts[1].is_empty()\n        && parts[2] == \"locations\" && !parts[3].is_empty()\n        && parts[4] == \"templates\" && !parts[5].is_empty()\n}","typeGuard":null,"tryCatchPattern":"match modelarmor::sanitize_text(&template, text).await {\n    Ok(res) => { /* inspect res.filter_match / res.sanitization_details */ }\n    Err(GwsError::Other(e)) => {\n        let msg = e.to_string();\n        if msg.contains(\"status 404\") { eprintln!(\"template not found: {template}\"); }\n        else if msg.contains(\"status 403\") { eprintln!(\"enable modelarmor.googleapis.com and grant modelarmor.user\"); }\n        else { return Err(e.into()); }\n        std::process::exit(1);\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Store the fully-qualified template name (projects/P/locations/L/templates/T) in your pipeline config, never a bare id.","Create templates with +create-template once in provisioning, verify with the Discovery get command, and reference that exact name.","Keep the Model Armor API enabled and the caller's role granted in the template's project — 403 is almost always one of those two."],"tags":["modelarmor","google-api","http-status","sanitize","gcp"],"backgroundTag":"google-api-error-response","analyzedSha":"a3768d0e82ad83cca2da97724e46bea4ff0e6dbd","analyzedAt":"2026-08-16T19:51:46.516Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}