{"record":{"id":"38f2a2a5270bd15f","repo":"Billionmail/BillionMail","slug":"invalid-api-key","errorCode":null,"errorMessage":"invalid API key","messagePattern":"invalid API key","errorType":"validation","errorClass":null,"httpStatus":401,"severity":"error","filePath":"core/internal/service/askai/supplier.go","lineNumber":543,"sourceCode":"\tclient := &http.Client{Timeout: 15 * time.Second}\n\treq, err := http.NewRequest(\"GET\", apiUrl, nil)\n\tif err != nil {\n\t\treturn err\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+apiKey)\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer resp.Body.Close()\n\n\tswitch resp.StatusCode {\n\tcase http.StatusOK:\n\t\treturn nil // API key is successfully validated\n\tcase http.StatusUnauthorized:\n\t\treturn errors.New(\"invalid API key\")\n\tcase http.StatusNotFound:\n\t\treturn nil\n\tdefault:\n\t\treturn fmt.Errorf(\"unexpected API response: %d %s\",\n\t\t\tresp.StatusCode, http.StatusText(resp.StatusCode))\n\t}\n}\n\n// GetSupplierConfig retrieves the configuration for a specific supplier by its name.\n// It reads the configuration file and returns a Supplier struct containing the supplier's details.\n// If the configuration file does not exist or an error occurs, it returns an error.\nfunc GetSupplierConfig(supplierName string) (*Supplier, error) {\n\treturn ReadSupplierConfig(supplierName)\n}\n\n// SetSupplierStatus updates the status of a supplier in its configuration file.\n// It reads the existing configuration, modifies the status, and saves it back to the file.\nfunc SetSupplierStatus(supplierName string, status bool) error {","sourceCodeStart":525,"sourceCodeEnd":561,"githubUrl":"https://github.com/Billionmail/BillionMail/blob/fc36c76c050c3775c5e899faf7403cf0262d2744/core/internal/service/askai/supplier.go#L525-L561","documentation":"Inside testAPIKeyValidity, a 401 Unauthorized response from the provider's models endpoint is mapped to the literal error 'invalid API key'. It means the request reached the provider but the credentials were rejected.","triggerScenarios":"testAPIKeyValidity receives HTTP 401 from GET <baseUrl>/models — the key is wrong, revoked, expired, or formatted incorrectly for the Authorization header.","commonSituations":"Typo or truncation when pasting the key; key belongs to a different provider than the baseUrl; key was rotated/revoked; missing or wrongly prefixed auth header ('Bearer ' vs 'Api-Key ') for a non-OpenAI-compatible provider.","solutions":["Regenerate/re-copy the API key from the provider dashboard and update the config","Confirm the key belongs to the same provider as the baseUrl (don't mix providers)","Check the key's required header format for the provider (Bearer vs custom)","Remove whitespace/newlines around the key value","Verify the key has not expired or been revoked"],"exampleFix":"// before\ncfg.ApiKey = os.Getenv(\"KEY\") // value had trailing newline\n// after\ncfg.ApiKey = strings.TrimSpace(os.Getenv(\"KEY\"))\nerr := askai.Testing(name, cfg.BaseUrl, cfg.ApiKey)","handlingStrategy":"validation","validationCode":"func keyFormatLooksValid(key string) bool {\n    k := strings.TrimSpace(key)\n    // OpenAI-style keys start with sk- and have no whitespace\n    return k != \"\" && !strings.ContainsAny(k, \" \\n\\t\\r\") && len(k) >= 20\n}","typeGuard":null,"tryCatchPattern":"if err := askai.Testing(name, baseUrl, apiKey); err != nil {\n    if err.Error() == \"invalid API key\" {\n        return fmt.Errorf(\"the provider rejected the API key (401); regenerate it and update config\")\n    }\n    return err\n}","preventionTips":["Store keys in a secret manager, not hardcoded or hand-typed","Rotate and re-verify keys after any provider-side rotation","Match key to provider endpoint exactly","Strip surrounding whitespace when loading keys from env or files"],"tags":["go","authentication","api-key","http-401","supplier"],"backgroundTag":"invalid-api-key","analyzedSha":"fc36c76c050c3775c5e899faf7403cf0262d2744","analyzedAt":"2026-09-05T21:28:54.019Z","contentChangedAt":"2026-09-05T21:28:54.019Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}