usememos/memos · error

OpenAI API key is required

Error message

OpenAI API key is required

What it means

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.

Solutions

  1. Set the API key in the AI provider settings for the OpenAI-compatible provider and retry.
  2. Verify the secret actually reaches the process (container env, secret store) — reference by name, never log it.
  3. For local no-auth endpoints, pass any non-empty placeholder key if your setup allows it.
  4. Confirm you selected the right provider type; a dedicated STT provider may be the wrong choice (see ai.ErrSTTNotSupported).

Example fix

// before
cfg := ai.ProviderConfig{Endpoint: "https://api.openai.com/v1"}
t, err := openai.New(cfg, opts) // err: OpenAI API key is required

// after
cfg := ai.ProviderConfig{Endpoint: "https://api.openai.com/v1", APIKey: os.Getenv("MEMOS_OPENAI_API_KEY")}
if cfg.APIKey == "" {
  return errors.New("MEMOS_OPENAI_API_KEY not set")
}
t, err := openai.New(cfg, opts)
Defensive patterns

Strategy: validation

Validate before calling

// Go — gate on the key by name, never log it
if strings.TrimSpace(cfg.APIKey) == "" {
  return errors.New("AI provider API key is not configured (check provider settings / injected secret)")
}
_, err := openai.New(cfg, opts)

Try / catch

t, err := openai.New(cfg, opts)
if err != nil {
  if strings.Contains(err.Error(), "API key is required") {
    return status.Errorf(codes.FailedPrecondition, "AI provider not configured: add an API key in settings")
  }
  return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of usememos/memos@14d757ce1f (2026-08-15). Data as JSON: /api/errors/98bf6b20ecff4eef. Report an issue: GitHub.

Appendix: source

Thrown at internal/ai/stt/openai/openai.go:34

	"github.com/usememos/memos/internal/ai"
	"github.com/usememos/memos/internal/ai/stt"
)

const defaultEndpoint = "https://api.openai.com/v1"

// Transcriber implements stt.Transcriber for OpenAI-compatible STT endpoints.
type Transcriber struct {
	client openaisdk.Client
}

// New constructs a Transcriber from a provider config.
func New(cfg ai.ProviderConfig, options stt.Options) (*Transcriber, error) {
	endpoint, err := normalizeEndpoint(cfg.Endpoint)
	if err != nil {
		return nil, err
	}
	if cfg.APIKey == "" {
		return nil, errors.New("OpenAI API key is required")
	}
	return &Transcriber{
		client: openaisdk.NewClient(
			openaioption.WithAPIKey(cfg.APIKey),
			openaioption.WithBaseURL(endpoint),
			openaioption.WithHTTPClient(options.HTTPClient),
		),
	}, nil
}

// Transcribe sends the audio to /audio/transcriptions.
func (t *Transcriber) Transcribe(ctx context.Context, req stt.Request) (*stt.Response, error) {
	if strings.TrimSpace(req.Model) == "" {
		return nil, errors.New("model is required")
	}
	if req.Audio == nil {
		return nil, errors.New("audio is required")
	}

View on GitHub (pinned to 14d757ce1f)