{"record":{"id":"ed154172a3498898","repo":"nocobase/nocobase","slug":"apikey-is-required","errorCode":null,"errorMessage":"apiKey is required","messagePattern":"apiKey is required","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugins/@nocobase/plugin-ai/src/server/llm-providers/provider.ts","lineNumber":479,"sourceCode":"}\n\nexport abstract class EmbeddingProvider {\n  protected app: Application;\n  protected serviceOptions?: Record<string, any>;\n  protected modelOptions?: Record<string, any>;\n  constructor(protected opts: EmbeddingProviderOptions) {\n    const { app, serviceOptions, modelOptions } = this.opts;\n    this.app = app;\n    this.serviceOptions = resolveServiceOptions(serviceOptions, app);\n    this.modelOptions = modelOptions;\n  }\n  abstract createEmbedding(): EmbeddingsInterface;\n  protected abstract getDefaultUrl(): string;\n\n  protected get apiKey() {\n    const { apiKey } = this.serviceOptions ?? {};\n    if (!apiKey) {\n      throw new Error('apiKey is required');\n    }\n    return apiKey;\n  }\n\n  protected get baseURL() {\n    const baseURL = getServiceBaseURL(this.serviceOptions) ?? this.getDefaultUrl();\n    if (!baseURL) {\n      throw new Error('baseURL is required');\n    }\n    return normalizeBaseURL(baseURL);\n  }\n\n  protected get model() {\n    const { model } = this.modelOptions ?? {};\n    if (!model) {\n      throw new Error('Embedding model is required');\n    }\n    return model;","sourceCodeStart":461,"sourceCodeEnd":497,"githubUrl":"https://github.com/nocobase/nocobase/blob/fa42722fefe44265490dff2c27d79e2882bce4fa/packages/plugins/@nocobase/plugin-ai/src/server/llm-providers/provider.ts#L461-L497","documentation":"EmbeddingProvider's `apiKey` getter reads apiKey from this.serviceOptions and throws 'apiKey is required' when it is missing or empty. Any embedding operation that touches this getter (e.g. createEmbedding building the underlying embeddings client) fails immediately. It is a fail-fast guard ensuring credentials exist before constructing API clients.","triggerScenarios":"Creating an embedding provider with serviceOptions lacking apiKey — provider record saved without a key, key field renamed/empty after config import, or env-resolved credentials evaluating to undefined when createEmbedding()/embedding requests run.","commonSituations":"Embeddings configured in NocoBase AI settings with the key left blank; credentials migrated between environments (staging export missing secrets); key stored in a secret manager that returned nothing; upgrading the plugin changed serviceOptions schema so the key no longer resolves.","solutions":["Edit the embedding provider settings in the AI plugin and save a valid apiKey.","Verify the stored serviceOptions for the provider record actually contain apiKey (check the ai providers collection/db row).","If keys come from environment variables or a secret manager, confirm they are present in the server process at startup.","Re-test with a direct API call using the key to confirm it is valid, not just present."],"exampleFix":"// before\ncreateEmbeddingProvider(app, { serviceOptions: {} });\n// after\ncreateEmbeddingProvider(app, { serviceOptions: { apiKey: process.env.OPENAI_API_KEY } });","handlingStrategy":"validation","validationCode":"// before creating the embedding provider\nif (!serviceOptions?.apiKey) {\n  throw new Error('Embedding provider settings must include an apiKey');\n}","typeGuard":"function isEmbeddingConfig(o: unknown): o is { serviceOptions: { apiKey: string } } {\n  return typeof o === 'object' && o !== null && typeof (o as any).serviceOptions?.apiKey === 'string' && (o as any).serviceOptions.apiKey.length > 0;\n}","tryCatchPattern":"try {\n  const embeddings = provider.createEmbedding();\n} catch (err) {\n  if (err.message === 'apiKey is required') {\n    // redirect user to provider settings to add the API key\n  } else throw err;\n}","preventionTips":["Enforce apiKey presence when saving embedding provider settings.","Use a settings health-check endpoint that touches provider getters once at startup.","Keep secrets out of config exports; re-inject them per environment.","Watch for schema renames in serviceOptions after upgrades."],"tags":["configuration","missing-credential","api-key","embeddings"],"backgroundTag":"missing-api-key","analyzedSha":"fa42722fefe44265490dff2c27d79e2882bce4fa","analyzedAt":"2026-09-01T00:54:31.202Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}