Hmbown/CodeWhale · error

bearer credentials are not an API key

Error message

bearer credentials are not an API key

What it means

resolve_api_key only hands off real API keys. When the resolved source is not RuntimeApiKeySource::Cli and the provider is OpenaiCodex, credentials come from bearer/OAuth token storage, which is not an API key, so the handoff bails. For other providers, OAuth bearer credentials are separately rejected by the ensure! on the next lines unless an API key is actually configured.

Solutions

  1. Provide an actual API key: `codewhale credential set <provider> <key>` or pass it via the CLI/override so api_key_source becomes Cli.
  2. For OpenaiCodex, use the bearer-token handoff path instead of the API-key handoff — the two credential kinds have separate surfaces.
  3. For xai, ensure an API key is configured (the code checks xai_auth_diagnostics().evaluates_runtime_api_key()); for moonshot, note bearer-only accounts also fail the key check.

Example fix

// before (OAuth-only Codex login)
codewhale credential api-key openai-codex   # -> bearer credentials are not an API key
// after
codewhale credential set openai-codex sk-...
codewhale credential api-key openai-codex
Defensive patterns

Strategy: type-guard

Validate before calling

let resolved = store.config.resolve_runtime_options_with_secrets(&overrides, &secrets);
if resolved.api_key_source != Some(RuntimeApiKeySource::Cli) {
    eprintln!("credential source is not a CLI API key (bearer/OAuth); set an API key for this provider");
}

Type guard

fn has_api_key(resolved: &ResolvedOptions) -> bool {
    resolved.api_key_source == Some(RuntimeApiKeySource::Cli)
}

Try / catch

match resolve_api_key(&store, &overrides, provider) {
    Err(e) if e.to_string().contains("not an API key") => {
        eprintln!("provider uses OAuth bearer auth; run the token handoff or set an API key");
    }
    other => other,
}

Prevention

When it happens

Trigger: Requesting an API key for OpenaiCodex while the resolved credential source is a bearer/OAuth token rather than a CLI-provided key; or, for other providers, when only OAuth bearer auth is configured and no API key resolves.

Common situations: Logging into Codex via OAuth (`codex` login flow) and then asking the handoff for an API key; xai/moonshot accounts authenticated with OAuth only; scripts expecting a static key but the machine uses token-based auth.

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 Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/807c6ed638e9f856. Report an issue: GitHub.

Appendix: source

Thrown at crates/cli/src/credential_handoff.rs:40

}

pub(crate) fn resolve_api_key(
    store: &ConfigStore,
    secrets: &Secrets,
    provider: ProviderKind,
    runtime_overrides: &CliRuntimeOverrides,
) -> Result<String> {
    let resolved = store.config.resolve_runtime_options_with_secrets(
        &runtime_overrides_for_provider(runtime_overrides, provider),
        secrets,
    );
    if resolved.provider != provider {
        bail!("resolved a different provider");
    }
    let source = resolved.api_key_source;
    if source != Some(RuntimeApiKeySource::Cli) {
        if provider == ProviderKind::OpenaiCodex {
            bail!("bearer credentials are not an API key");
        }
        let uses_api_key = provider != ProviderKind::Xai
            || xai_auth_diagnostics(store, runtime_overrides).evaluates_runtime_api_key();
        ensure!(uses_api_key, "OAuth bearer credentials are not an API key");
        let kimi_bearer = provider == ProviderKind::Moonshot
            && resolved
                .auth_mode
                .as_deref()
                .is_some_and(auth_mode_uses_kimi_imported_token);
        ensure!(!kimi_bearer, "bearer credentials are not an API key");
    }
    ensure!(source.is_some(), "no runtime-effective API key");
    resolved
        .api_key
        .filter(|value| !value.trim().is_empty())
        .context("no usable runtime-effective API key")
}

View on GitHub (pinned to 73e0f67d83)