{"record":{"id":"7e323d05793e90e4","repo":"windmill-labs/windmill","slug":"cannot-test-api-key-for-custom-ai-only-resource-p","errorCode":null,"errorMessage":"Cannot test API key for Custom AI, only resource path is supported","messagePattern":"Cannot test API key for Custom AI, only resource path is supported","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"frontend/src/lib/components/copilot/lib.ts","lineNumber":996,"sourceCode":"\t}\n\n\tconst fetchOptions: {\n\t\tsignal: AbortSignal\n\t\theaders: Record<string, string>\n\t} = {\n\t\tsignal: abortController.signal,\n\t\theaders: {\n\t\t\t'X-Provider': provider\n\t\t}\n\t}\n\tif (options?.resourcePath) {\n\t\tfetchOptions.headers = {\n\t\t\t...fetchOptions.headers,\n\t\t\t'X-Resource-Path': options.resourcePath\n\t\t}\n\t} else if (options?.apiKey) {\n\t\tif (provider === 'customai') {\n\t\t\tthrow new Error('Cannot test API key for Custom AI, only resource path is supported')\n\t\t}\n\n\t\tfetchOptions.headers = {\n\t\t\t...fetchOptions.headers,\n\t\t\t'X-API-Key': options.apiKey\n\t\t}\n\t}\n\tconst openaiClient = options?.apiKey\n\t\t? createOpenAIProxyClient(getAiProxyBaseURL())\n\t\t: options?.workspace\n\t\t\t? workspaceAIClients.createOpenaiClient(options.workspace)\n\t\t\t: workspaceAIClients.getOpenaiClient()\n\n\tconst completion = await openaiClient.chat.completions.create(config, fetchOptions)\n\tresponse = completion.choices?.[0]?.message.content || ''\n\treturn response\n}\n","sourceCodeStart":978,"sourceCodeEnd":1014,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/frontend/src/lib/components/copilot/lib.ts#L978-L1014","documentation":"Custom AI (customai, an Azure-OpenAI-style provider) authenticates via a resource path header, not an API key. testKey() explicitly rejects apiKey-only configs for this provider because a key alone produces an untestable request; only resourcePath is supported for the test.","triggerScenarios":"Calling testKey() with aiProvider 'customai' and options.apiKey set while options.resourcePath is unset — e.g. pasting an API key into the Custom AI settings and clicking Test without filling the resource path.","commonSituations":"Users coming from OpenAI-style providers assume a key is the credential; a form that submits both fields but leaves resourcePath blank; migrated configs that carried only a key.","solutions":["Set the Custom AI resource path in the AI settings and re-run the test.","Clear the API key field if the form routes the request into the apiKey branch; ensure resourcePath is the populated credential.","If you have only a key (no Azure-style resource), configure a plain OpenAI-compatible provider instead of customai.","Check the workspace AI config stored value actually contains resourcePath (it may not have been saved)."],"exampleFix":"// before\nawait testKey({ aiProvider: 'customai', apiKey: 'sk-...', ... })\n// after\nawait testKey({ aiProvider: 'customai', resourcePath: 'my-resource/deployment', ... })","handlingStrategy":"validation","validationCode":"if (provider === 'customai' && !resourcePath?.trim()) {\n  showToast('Custom AI requires a resource path to test')\n  return\n}","typeGuard":"function customaiConfigOk(opts: { provider: string; resourcePath?: string }): boolean {\n  return opts.provider !== 'customai' || Boolean(opts.resourcePath?.trim())\n}","tryCatchPattern":"try {\n  await testKey({ aiProvider: 'customai', resourcePath, ... })\n} catch (e) {\n  if (e.message.includes('Custom AI')) {\n    showToast('Custom AI testing uses a resource path, not an API key')\n  } else throw e\n}","preventionTips":["In the Custom AI settings form, require the resource-path field and de-emphasize the key field.","Document per-provider credential requirements next to the inputs.","Normalize migrated configs to attach resourcePath for customai entries."],"tags":["validation","configuration","azure","customai","api-key"],"backgroundTag":"wrong-credential-type","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}