earendil-works/pi · error

No API key for provider: ${provider}

Error message

No API key for provider: ${provider}

What it means

Before building an Anthropic SDK client, the anthropic-messages stream function asserts authentication via assertRequestAuth: either options.apiKey is set, or the merged headers contain a non-empty authorization, x-api-key, or cf-aig-authorization value (case-insensitive keys). If none is present it throws 'No API key for provider: <provider>' (e.g. anthropic, github-copilot). Supplying options.client - such as an AnthropicVertex instance - skips the check entirely because auth lives inside that client.

Source

Thrown at packages/ai/src/api/anthropic-messages.ts:306

function hasHeader(headers: ProviderHeaders | undefined, name: string): boolean {
	if (!headers) return false;
	const expected = name.toLowerCase();
	for (const [key, value] of Object.entries(headers)) {
		if (key.toLowerCase() === expected && value !== null && value.trim().length > 0) return true;
	}
	return false;
}

function assertRequestAuth(provider: string, apiKey: string | undefined, headers: ProviderHeaders | undefined): void {
	if (apiKey) return;
	if (
		hasHeader(headers, "authorization") ||
		hasHeader(headers, "x-api-key") ||
		hasHeader(headers, "cf-aig-authorization")
	) {
		return;
	}
	throw new Error(`No API key for provider: ${provider}`);
}

interface ServerSentEvent {
	event: string | null;
	data: string;
	raw: string[];
}

interface SseDecoderState {
	event: string | null;
	data: string[];
	raw: string[];
}

const ANTHROPIC_MESSAGE_EVENTS: ReadonlySet<string> = new Set([
	"message_start",
	"message_delta",
	"message_stop",

View on GitHub (pinned to 4af9d21d3b)

Solutions

  1. Set the apiKey option when building stream options, typically read from the provider's env var at startup
  2. Or pass an auth header: authorization (Bearer ...), x-api-key, or cf-aig-authorization for Cloudflare AI Gateway
  3. Or inject options.client (e.g. AnthropicVertex) when auth is not key-based
  4. Fail fast at startup: assert the key resolves before the first request, not mid-conversation

Example fix

// before
const result = await streamSimple(model, context, {}); // no apiKey, no headers

// after
const apiKey = process.env.ANTHROPIC_API_KEY;
if (!apiKey) throw new Error("ANTHROPIC_API_KEY is not set");
const result = await streamSimple(model, context, { apiKey });
Defensive patterns

Strategy: validation

Validate before calling

const hasAuth = Boolean(
  apiKey ||
  headers?.authorization ||
  headers?.["x-api-key"] ||
  headers?.["cf-aig-authorization"],
);
if (!hasAuth && !options?.client) {
  throw new Error(`No API key for provider: ${model.provider}`);
}
await stream(model, context, { ...options, apiKey, headers });

Type guard

const canAuthenticate = (opts: { apiKey?: string; headers?: Record<string, string>; client?: unknown }): boolean =>
  Boolean(opts.client || opts.apiKey || hasNonEmptyAuthHeader(opts.headers));

Prevention

When it happens

Trigger: Calling stream or streamSimple on an anthropic-messages model with no apiKey option, no auth headers, and no custom client; the host resolves the key from an env var that is unset in the current shell, container, or CI runner; the key lookup returns empty string.

Common situations: ANTHROPIC_API_KEY (or the host's own env mapping) missing in CI, Docker, or a deployed environment; .env loaded after stream options are built; gateway setups (Cloudflare AI Gateway, Vertex) that should pass cf-aig-authorization headers or options.client instead of an API key.

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 earendil-works/pi@4af9d21d3b (2026-08-24). Data as JSON: /api/errors/ec37dc688ae555b7. Report an issue: GitHub.