windmill-labs/windmill · error

Cannot test API key for Custom AI, only resource path is sup

Error message

Cannot test API key for Custom AI, only resource path is supported

What it means

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.

Source

Thrown at frontend/src/lib/components/copilot/lib.ts:996

	}

	const fetchOptions: {
		signal: AbortSignal
		headers: Record<string, string>
	} = {
		signal: abortController.signal,
		headers: {
			'X-Provider': provider
		}
	}
	if (options?.resourcePath) {
		fetchOptions.headers = {
			...fetchOptions.headers,
			'X-Resource-Path': options.resourcePath
		}
	} else if (options?.apiKey) {
		if (provider === 'customai') {
			throw new Error('Cannot test API key for Custom AI, only resource path is supported')
		}

		fetchOptions.headers = {
			...fetchOptions.headers,
			'X-API-Key': options.apiKey
		}
	}
	const openaiClient = options?.apiKey
		? createOpenAIProxyClient(getAiProxyBaseURL())
		: options?.workspace
			? workspaceAIClients.createOpenaiClient(options.workspace)
			: workspaceAIClients.getOpenaiClient()

	const completion = await openaiClient.chat.completions.create(config, fetchOptions)
	response = completion.choices?.[0]?.message.content || ''
	return response
}

View on GitHub (pinned to e474e8803c)

Solutions

  1. Set the Custom AI resource path in the AI settings and re-run the test.
  2. Clear the API key field if the form routes the request into the apiKey branch; ensure resourcePath is the populated credential.
  3. If you have only a key (no Azure-style resource), configure a plain OpenAI-compatible provider instead of customai.
  4. Check the workspace AI config stored value actually contains resourcePath (it may not have been saved).

Example fix

// before
await testKey({ aiProvider: 'customai', apiKey: 'sk-...', ... })
// after
await testKey({ aiProvider: 'customai', resourcePath: 'my-resource/deployment', ... })
Defensive patterns

Strategy: validation

Validate before calling

if (provider === 'customai' && !resourcePath?.trim()) {
  showToast('Custom AI requires a resource path to test')
  return
}

Type guard

function customaiConfigOk(opts: { provider: string; resourcePath?: string }): boolean {
  return opts.provider !== 'customai' || Boolean(opts.resourcePath?.trim())
}

Try / catch

try {
  await testKey({ aiProvider: 'customai', resourcePath, ... })
} catch (e) {
  if (e.message.includes('Custom AI')) {
    showToast('Custom AI testing uses a resource path, not an API key')
  } else throw e
}

Prevention

When it happens

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

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

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/7e323d05793e90e4. Report an issue: GitHub.