Hmbown/CodeWhale · error
OAuth bearer credentials are not an API key
Error message
OAuth bearer credentials are not an API key
What it means
resolve_api_key ensures the resolved credential for the provider is an actual API key, not an OAuth bearer token. For non-Cli sources it checks whether the provider authenticates via API keys (xai is exempt from the check) and rejects the handoff when the effective auth mode is OAuth bearer, since bearer tokens cannot be handed off as API keys.
Solutions
- Log in to the provider with an API key (set the key via the normal key-entry path) so the runtime slot holds an actual API key.
- Select a different provider whose runtime credential is an API key, or override with a CLI-provided key (RuntimeApiKeySource::Cli bypasses this check).
- For Moonshot, re-authenticate without the imported Kimi token (auth_mode must not be the kimi imported-token mode).
- If you need the bearer token itself, use an auth-mode-aware handoff instead of the API-key handoff.
Example fix
// before $ codewhale handoff api-key --provider moonshot # auth_mode = imported kimi token -> refused // after $ codewhale auth login --provider moonshot --api-key $ codewhale handoff api-key --provider moonshot
Defensive patterns
Strategy: validation
Validate before calling
// before requesting an API-key handoff, confirm the provider's auth mode is key-based
let resolved = resolve_provider(provider)?;
if resolved.auth_mode.is_some() {
eprintln!("provider uses OAuth bearer auth; API-key handoff unavailable");
} Type guard
fn is_api_key_provider(provider: ProviderKind, store: &SecretStore) -> bool {
match provider {
ProviderKind::OpenaiCodex | ProviderKind::Xai => false,
_ => store.api_key_for(provider).map(|k| !k.trim().is_empty()).unwrap_or(false),
}
} Try / catch
match resolve_api_key(provider, store, overrides) {
Err(e) if e.to_string().contains("are not an API key") => {
eprintln!("{provider} is authenticated via OAuth; log in with an API key or hand off the bearer token instead");
}
Err(e) => return Err(e),
Ok(k) => Ok(k),
} Prevention
- Track which providers are OAuth-based per account and skip key-handoff for them.
- Authenticate with an API key when you intend to use key-based tooling.
- Use CLI-provided key overrides when you must force a key path.
- Check auth_mode before choosing between key handoff and token handoff.
When it happens
Trigger: Requesting an API key handoff for a provider whose runtime credential came from OAuth login (e.g. OpenAI Codex bearer, or Moonshot with an imported Kimi token), so the store resolves a bearer credential rather than an API key.
Common situations: User authenticated a provider via OAuth flow but then runs the API-key handoff command; a shared secret store holds a bearer token in the provider's slot; xai/non-key providers configured as the runtime provider while an API key is requested.
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
- 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/4f3a5abc71864834.
Report an issue: GitHub.
Appendix: source
Thrown at crates/cli/src/credential_handoff.rs:44
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")
}
pub(crate) fn handoff_secret_line(
writer: &mut impl Write,
stdout_is_terminal: bool,
resolve: impl FnOnce() -> Result<String>,View on GitHub (pinned to 73e0f67d83)