{"record":{"id":"98bf6b20ecff4eef","repo":"usememos/memos","slug":"openai-api-key-is-required","errorCode":null,"errorMessage":"OpenAI API key is required","messagePattern":"OpenAI API key is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/ai/stt/openai/openai.go","lineNumber":34,"sourceCode":"\t\"github.com/usememos/memos/internal/ai\"\n\t\"github.com/usememos/memos/internal/ai/stt\"\n)\n\nconst defaultEndpoint = \"https://api.openai.com/v1\"\n\n// Transcriber implements stt.Transcriber for OpenAI-compatible STT endpoints.\ntype Transcriber struct {\n\tclient openaisdk.Client\n}\n\n// New constructs a Transcriber from a provider config.\nfunc New(cfg ai.ProviderConfig, options stt.Options) (*Transcriber, error) {\n\tendpoint, err := normalizeEndpoint(cfg.Endpoint)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif cfg.APIKey == \"\" {\n\t\treturn nil, errors.New(\"OpenAI API key is required\")\n\t}\n\treturn &Transcriber{\n\t\tclient: openaisdk.NewClient(\n\t\t\topenaioption.WithAPIKey(cfg.APIKey),\n\t\t\topenaioption.WithBaseURL(endpoint),\n\t\t\topenaioption.WithHTTPClient(options.HTTPClient),\n\t\t),\n\t}, nil\n}\n\n// Transcribe sends the audio to /audio/transcriptions.\nfunc (t *Transcriber) Transcribe(ctx context.Context, req stt.Request) (*stt.Response, error) {\n\tif strings.TrimSpace(req.Model) == \"\" {\n\t\treturn nil, errors.New(\"model is required\")\n\t}\n\tif req.Audio == nil {\n\t\treturn nil, errors.New(\"audio is required\")\n\t}","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/internal/ai/stt/openai/openai.go#L16-L52","documentation":"Returned by openai.New (internal/ai/stt/openai) when ai.ProviderConfig.APIKey is the empty string. The OpenAI-compatible SDK client is constructed with WithAPIKey(cfg.APIKey), and an empty key would only fail later at the HTTP layer with 401s, so construction fails fast instead.","triggerScenarios":"Creating the OpenAI STT transcriber with a provider config whose APIKey was never populated — e.g., the Memos AI provider settings saved without a key, or the env/source feeding ProviderConfig returned an empty string. Happens on the first transcription attempt or at provider construction.","commonSituations":"Fresh AI provider setup where the key field was left blank; secret env var (OPENAI_API_KEY) not injected into the container; using a local OpenAI-compatible server (whisper.cpp, vLLM) that needs no auth but the code path still requires a non-empty key string.","solutions":["Set the API key in the AI provider settings for the OpenAI-compatible provider and retry.","Verify the secret actually reaches the process (container env, secret store) — reference by name, never log it.","For local no-auth endpoints, pass any non-empty placeholder key if your setup allows it.","Confirm you selected the right provider type; a dedicated STT provider may be the wrong choice (see ai.ErrSTTNotSupported)."],"exampleFix":"// before\ncfg := ai.ProviderConfig{Endpoint: \"https://api.openai.com/v1\"}\nt, err := openai.New(cfg, opts) // err: OpenAI API key is required\n\n// after\ncfg := ai.ProviderConfig{Endpoint: \"https://api.openai.com/v1\", APIKey: os.Getenv(\"MEMOS_OPENAI_API_KEY\")}\nif cfg.APIKey == \"\" {\n  return errors.New(\"MEMOS_OPENAI_API_KEY not set\")\n}\nt, err := openai.New(cfg, opts)","handlingStrategy":"validation","validationCode":"// Go — gate on the key by name, never log it\nif strings.TrimSpace(cfg.APIKey) == \"\" {\n  return errors.New(\"AI provider API key is not configured (check provider settings / injected secret)\")\n}\n_, err := openai.New(cfg, opts)","typeGuard":null,"tryCatchPattern":"t, err := openai.New(cfg, opts)\nif err != nil {\n  if strings.Contains(err.Error(), \"API key is required\") {\n    return status.Errorf(codes.FailedPrecondition, \"AI provider not configured: add an API key in settings\")\n  }\n  return err\n}","preventionTips":["Validate provider config (key present, endpoint normalized) at settings-save time.","Inject secrets via env/secret store; verify presence at boot with a non-empty assert.","For no-auth local endpoints, require an explicit placeholder key in config rather than empty."],"tags":["ai","stt","openai","configuration","api-key","go"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}