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
- Provide an actual API key: `codewhale credential set <provider> <key>` or pass it via the CLI/override so api_key_source becomes Cli.
- For OpenaiCodex, use the bearer-token handoff path instead of the API-key handoff — the two credential kinds have separate surfaces.
- 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
- Set explicit API keys (`credential set <provider> <key>`) for providers consumed by the key handoff.
- Remember OpenaiCodex OAuth login yields bearer tokens, not API keys.
- In scripts, check the credential source before expecting a static key.
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
- OAuth bearer credentials are not an API key
- agy OAuth token JSON carries no access token member
- agy OAuth token member
- api_key cannot be empty string
- atomically replacing xAI OAuth credentials
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)