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

  1. Inspect the embedded HTTP status and body: 403 usually means org policy/permissions forbid key creation; 401 means the access token was rejected.
  2. Verify the account has API key creation enabled (org settings, admin role, active billing).
  3. Re-run the login flow to get a fresh access token, in case the one used had expired or was scope-limited.
  4. If it was a transient 5xx or rate limit, wait and retry the login.
  5. 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

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


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)