Kuberwastaken/claurst · error
API key creation failed
Error message
API key creation failed ({}): {} What it means
Raised in create_api_key when the authenticated POST that mints a Console API key (using the freshly obtained OAuth access token) returns a non-success HTTP status. The status code and response body are embedded in the message; this failure is one of the paths that leads to error 25 (no usable credential) because finalize_login converts the Err into a warning and proceeds with api_key = None.
Solutions
- Inspect the embedded HTTP status and body: 403 usually means org policy/permissions forbid key creation; 401 means the access token was rejected.
- Verify the account has API key creation enabled (org settings, admin role, active billing).
- Re-run the login flow to get a fresh access token, in case the one used had expired or was scope-limited.
- If it was a transient 5xx or rate limit, wait and retry the login.
- If the provider changed the endpoint path or response schema, update create_api_key / CreateApiKeyResponse accordingly.
Example fix
// before: failure is downgraded to a warning, producing a confusing follow-up error
Err(e) => {
warn!("Failed to create API key from OAuth token: {}", e);
None
}
// after: propagate immediately so the user sees the true server reason
let api_key = create_api_key(&token_resp.access_token).await
.context("Console API key creation failed during login")?; Defensive patterns
Strategy: retry
Validate before calling
// Pre-flight: confirm the access token exists and the account should be able to mint keys
let access_token = token_resp.access_token;
ensure!(!access_token.is_empty(), "missing access token for key creation");
// Check response shape early to detect provider schema drift
let sample: Result<CreateApiKeyResponse, _> = serde_json::from_str("{}");
let _ = sample; // if fields changed upstream, update struct before deploying Type guard
fn is_key_creation_allowed_status(status: u16) -> bool {
// 401/403 mean policy/auth problems — retrying will not help
!(status == 401 || status == 403)
} Try / catch
match create_api_key(&access_token).await {
Ok(key) => /* proceed with Console credential */,
Err(e) if e.to_string().contains("429") || e.to_string().contains("503") => {
tokio::time::sleep(Duration::from_secs(5)).await;
create_api_key(&access_token).await? // one bounded retry
}
Err(e) => bail!("API key creation failed: {} — check org permissions/billing", e),
} Prevention
- Check the embedded HTTP status: 403 = org policy, 401 = token problem, 5xx/429 = transient
- Ensure the account has active billing and key-creation permissions before automating logins
- Retry only transient statuses (429, 5xx) with backoff; never retry 401/403
- Watch for provider API changes to the key-creation endpoint and response schema
When it happens
Trigger: create_api_key gets !resp.status().is_success() from the API key creation endpoint: the access token lacks the key-creation scope, the account/organization forbids key creation or has no billing, the access token has already expired, or the endpoint returned 404/500 due to an API change.
Common situations: Developers hit this with accounts on organizations that disable programmatic API key creation, free/trial accounts without payment setup, rate limiting after repeated logins, or when the provider moved the key-creation route in a newer API version.
Related errors
- Bridge register: server returned
- Token exchange failed
- Token exchange failed
- exchange_code: HTTP
- refresh: HTTP
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/f9739390f0fc9e1c.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/cli/src/oauth_flow.rs:432
}
/// Exchange an OAuth access token for an Anthropic API key (Console flow only).
async fn create_api_key(access_token: &str) -> anyhow::Result<String> {
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
let resp = client
.post(oauth::API_KEY_URL)
.header("Authorization", format!("Bearer {}", access_token))
.send()
.await
.context("API key creation request failed")?;
if !resp.status().is_success() {
let status = resp.status();
let text = resp.text().await.unwrap_or_default();
bail!("API key creation failed ({}): {}", status, text);
}
let data: CreateApiKeyResponse = resp.json().await.context("Failed to parse API key response")?;
data.raw_key.context("Server returned no API key")
}
// ---- Refresh token flow -----------------------------------------------------
/// Attempt to refresh an expired access token using the stored refresh token.
/// Saves updated tokens on success.
#[allow(dead_code)]
pub async fn refresh_oauth_token(tokens: &OAuthTokens) -> anyhow::Result<OAuthTokens> {
let refresh_token = tokens
.refresh_token
.as_deref()
.context("No refresh token available")?;
let body = serde_json::json!({
View on GitHub (pinned to b0637c97ec)