{"record":{"id":"a48760395f8e0e6e","repo":"SubtitleEdit/subtitleedit","slug":"api-key-invalid-or-perhaps-billing-api-is-not-ena","errorCode":null,"errorMessage":"API key invalid (or perhaps billing/API is not enabled)?","messagePattern":"API key invalid \\(or perhaps billing/API is not enabled\\)\\?","errorType":"exception","errorClass":"OcrException","httpStatus":400,"severity":"error","filePath":"src/libuilogic/Ocr/Service/GoogleCloudVisionApi.cs","lineNumber":204,"sourceCode":"            }\n\n            // Convert to JSON string\n            string requestBodyString;\n            using (var memoryStream = new MemoryStream())\n            {\n                new DataContractJsonSerializer(typeof(RequestBody)).WriteObject(memoryStream, requestBody);\n                requestBodyString = Encoding.Default.GetString(memoryStream.ToArray());\n            }\n\n            // Do request\n            var uri = $\"?key={_apiKey}\";\n            string content;\n            try\n            {\n                var result = _httpClient.PostAsync(uri, new StringContent(requestBodyString)).Result;\n                if ((int)result.StatusCode == 400)\n                {\n                    throw new OcrException(\"API key invalid (or perhaps billing/API is not enabled)?\");\n                }\n\n                if ((int)result.StatusCode == 403)\n                {\n                    throw new OcrException(\"\\\"Perhaps billing is not enabled (or API not enabled or API key is invalid)?\\\"\");\n                }\n\n                if (!result.IsSuccessStatusCode)\n                {\n                    throw new OcrException($\"An error occurred calling Cloud Vision API - status code: {result.StatusCode}\");\n                }\n\n                content = result.Content.ReadAsStringAsync().Result;\n            }\n            catch (WebException webException)\n            {\n                var message = string.Empty;\n                if (webException.Message.Contains(\"(400) Bad Request\"))","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/libuilogic/Ocr/Service/GoogleCloudVisionApi.cs#L186-L222","documentation":"OcrException thrown when the Google Cloud Vision REST call returns HTTP 400. 400 from the Vision API specifically means the request was malformed or the API key was rejected as invalid — billing/quota issues surface as 403 instead (see the next guard). The body is not inspected; only the status code drives the message.","triggerScenarios":"Posting to the Vision API with a missing, mistyped, revoked, or wrong-format _apiKey; or with a malformed request body the API rejects pre-auth.","commonSituations":"Pasted key with trailing whitespace; copied a service-account JSON instead of an API key; key deleted in GCP Console; environment mismatch (dev key in prod); typo in the key.","solutions":["Open GCP > APIs & Services > Credentials and confirm the API key still exists and is unrestricted for Vision.","Regenerate the key and re-enter it without surrounding spaces or quotes.","Make sure you are passing an API key (starts with AIza...) and not an OAuth token or service-account private key.","Re-run with the same body using curl and the same key to see Google's detailed error JSON."],"exampleFix":"// before\nif ((int)result.StatusCode == 400)\n    throw new OcrException(\"API key invalid (or perhaps billing/API is not enabled)?\");\n\n// after — surface Google's reason from the body so 400 (bad request) is distinguishable from auth\nif ((int)result.StatusCode == 400)\n{\n    var body = result.Content.ReadAsStringAsync().Result;\n    throw new OcrException($\"Cloud Vision API rejected the request (400). Body: {body}\");\n}","handlingStrategy":"validation","validationCode":"static void ValidateApiKey(string key)\n{\n    if (string.IsNullOrWhiteSpace(key)) throw new OcrException(\"Cloud Vision API key is empty.\");\n    if (!key.StartsWith(\"AIza\", StringComparison.Ordinal) || key.Length != 39) throw new OcrException(\"Key does not look like a Google API key (AIza..., 39 chars).\");\n}","typeGuard":"null","tryCatchPattern":"try { content = CallVision(...); }\ncatch (OcrException ex) when (ex.Message.Contains(\"API key invalid\")) { SeLogger.Error(\"Vision key rejected; verify GCP Credentials.\"); throw; }","preventionTips":["Trim the API key on input to kill trailing whitespace.","Validate the AIza prefix + length at config-save time, not just at call time.","Do a 1-image smoke test after entering a key to fail fast."],"tags":["ocr","google-cloud-vision","http-400","api-key","authentication"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}