{"record":{"id":"a40c845f0d6dd10f","repo":"SubtitleEdit/subtitleedit","slug":"perhaps-billing-is-not-enabled-or-api-not-enabled","errorCode":null,"errorMessage":"Perhaps billing is not enabled (or API not enabled or API key is invalid)?","messagePattern":"Perhaps billing is not enabled \\(or API not enabled or API key is invalid\\)\\?","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/libuilogic/AutoTranslate/GoogleTranslateV2.cs","lineNumber":102,"sourceCode":"                {\n                    throw new Exception($\"An error occurred calling GT translate - status code: {result.StatusCode}\");\n                }\n\n                content = await result.Content.ReadAsStringAsync(cancellationToken);\n            }\n            catch (WebException webException)\n            {\n                var message = string.Empty;\n                if (webException.Message.Contains(\"(400) Bad Request\"))\n                {\n                    message = \"API key invalid (or perhaps API/billing is not enabled)?\";\n                }\n                else if (webException.Message.Contains(\"(403) Forbidden.\"))\n                {\n                    message = \"Perhaps billing is not enabled (or API not enabled or API key is invalid)?\";\n                }\n\n                throw new Exception(message, webException);\n            }\n\n            var resultList = new List<string>();\n            var parser = new JsonParser();\n            var x = (Dictionary<string, object>)parser.Parse(content);\n            foreach (var k in x.Keys)\n            {\n                if (x[k] is Dictionary<string, object> v)\n                {\n                    foreach (var innerKey in v.Keys)\n                    {\n                        if (v[innerKey] is List<object> l)\n                        {\n                            foreach (var o2 in l)\n                            {\n                                if (o2 is Dictionary<string, object> v2)\n                                {\n                                    foreach (var innerKey2 in v2.Keys)","sourceCodeStart":84,"sourceCodeEnd":120,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/libuilogic/AutoTranslate/GoogleTranslateV2.cs#L84-L120","documentation":"Thrown inside GoogleTranslateV2's catch (WebException) block when the exception message contains '(403) Forbidden.'. Same legacy-exception path as error 50 but for the 403 case. Maps to a billing/API-not-enabled/key-invalid diagnosis. Shares the throw site and the empty-message-if-no-match pitfall with error 50.","triggerScenarios":"A WebException escapes with Message containing '(403) Forbidden.'; root cause is identical to error 48 (billing disabled, API not enabled, key invalid/restricted).","commonSituations":"Billing disabled; Translation API not enabled; key IP/referrer restrictions blocking the caller; runtime surfacing 403 as WebException.","solutions":["Enable billing and the Translation API on the GCP project.","Loosen API key restrictions to allow the caller IP.","Inspect the inner WebException Response body for Google's error.","Confirm the key is not disabled in the console."],"exampleFix":"// before\nelse if (webException.Message.Contains(\"(403) Forbidden.\"))\n{\n    message = \"Perhaps billing is not enabled (or API not enabled or API key is invalid)?\";\n}\n\n// after - fall back to a descriptive default so the exception is never empty\nvar message = $\"GoogleTranslateV2 transport error: {webException.Status} - {webException.Message}\";\nif (webException.Message.Contains(\"(400) Bad Request\")) { message = \"...\"; }\nelse if (webException.Message.Contains(\"(403) Forbidden.\")) { message = \"...\"; }\nthrow new Exception(message, webException);","handlingStrategy":"try-catch","validationCode":"// Probe with a cheap /languages call to catch the 403 (billing/API/key) before real work\nvar key = Configuration.Settings.Tools.GoogleApiV2Key;\nusing var c = new HttpClient();\nvar resp = await c.GetAsync($\"https://translation.googleapis.com/language/translate/v2/languages?key={key}\");\nif ((int)resp.StatusCode == 403)\n    throw new InvalidOperationException(\"Google returned 403 on setup probe - enable billing/Translation API or fix the key.\");","typeGuard":null,"tryCatchPattern":"try\n{\n    return await googleV2.Translate(text, src, tgt, token);\n}\ncatch (Exception ex) when (ex.InnerException is WebException we && we.Message.Contains(\"(403) Forbidden.\"))\n{\n    throw new InvalidOperationException(\"Google returned 403 - billing/API not enabled or key restricted/invalid.\", ex);\n}","preventionTips":["Enable billing on the GCP project (most common 403 cause).","Do not IP-restrict the key to a dynamic address.","Always inspect WebException.Response for Google's structured error rather than only string-matching.","Share the empty-message fix from error 50 - both branches use the same throw and the same pitfall applies."],"tags":["api","auth","billing","translation","google","transport"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}