windmill-labs/windmill · error

API key or resource path is required

Error message

API key or resource path is required

What it means

testKey() validates an AI provider credential by making a small test completion through the AI proxy. It requires at least one authentication means: an API key (sent as X-API-Key) or a resource path (sent as X-Resource-Path, used for Azure-like providers). If both are missing/empty the call cannot authenticate, so it throws immediately before any network request.

Source

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

export async function testKey({
	apiKey,
	workspace,
	resourcePath,
	model,
	abortController,
	messages,
	aiProvider
}: {
	apiKey?: string
	workspace?: string
	resourcePath?: string
	model: string | undefined
	messages: ChatCompletionMessageParam[]
	abortController: AbortController
	aiProvider: AIProvider
}) {
	if (!apiKey && !resourcePath) {
		throw new Error('API key or resource path is required')
	}
	const modelToTest = model ?? AI_PROVIDERS[aiProvider].defaultModels[0]

	if (!modelToTest) {
		throw new Error('Missing a model to test')
	}

	// getNonStreamingCompletion routes Anthropic-Messages-API models (native
	// Anthropic and Claude on Azure Foundry) through the Anthropic SDK and
	// everything else through OpenAI chat completions, so the test exercises the
	// same request shape the feature actually sends. The cap keeps max_tokens
	// under the Anthropic SDK's non-streaming pre-flight limit (~21k tokens),
	// which would otherwise reject the request before it is sent.
	await getNonStreamingCompletion(messages, abortController, {
		apiKey,
		workspace,
		resourcePath,
		forceModelProvider: {

View on GitHub (pinned to e474e8803c)

Solutions

  1. Enter an API key in the provider settings before clicking Test.
  2. For providers that authenticate by resource path (e.g. Azure), fill in the resource path instead of the key.
  3. Check that the settings form actually binds the apiKey/resourcePath inputs to the object passed to testKey.
  4. Confirm the stored workspace AI config retains the key (not stripped by sanitization before the call).

Example fix

// before
await testKey({ aiProvider: 'openai', model, messages, abortController })
// after
if (!apiKey && !resourcePath) { alert('Enter an API key first'); return }
await testKey({ aiProvider: 'openai', apiKey, model, messages, abortController })
Defensive patterns

Strategy: validation

Validate before calling

if (!apiKey?.trim() && !resourcePath?.trim()) {
  showToast('Enter an API key or resource path before testing')
  return
}

Type guard

function hasCredential(opts: { apiKey?: string; resourcePath?: string }): boolean {
  return Boolean(opts.apiKey?.trim() || opts.resourcePath?.trim())
}

Try / catch

try {
  await testKey({ aiProvider, apiKey, resourcePath, model, messages, abortController })
} catch (e) {
  if (e.message === 'API key or resource path is required') {
    showToast('A credential is required to test this provider')
  } else throw e
}

Prevention

When it happens

Trigger: Calling testKey({ apiKey: undefined, resourcePath: undefined, ... }) — e.g. the 'Test' button in the AI settings modal clicked with the key field left blank, or a saved config whose key was never persisted.

Common situations: User saves AI settings without pasting an API key; a provider that needs a resource path (Azure OpenAI / customai) configured with only an empty key field; config object spread losing the apiKey field.

Understand the failure class

Background: "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries — this error's family across 16 libraries.

Related errors


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