unslothai/unsloth · error · HTTPException

Failed to decrypt API key. The server public key may have ch

Error message

Failed to decrypt API key. The server public key may have changed — try refreshing the page.

What it means

A 400 raised when resolve_provider_api_key throws while decrypting the client-supplied encrypted_api_key. Keys are RSA-encrypted client-side with a server public key fetched from GET /api/providers/public-key; if the server keypair was regenerated (reinstall, key rotation, new install behind a stale page), old ciphertext cannot be decrypted and this error names the most common cause: refresh the page to pick up the current public key.

Source

Thrown at studio/backend/routes/provider_credentials.py:56


def resolve_provider_api_key_or_400(
    provider_id: str | None,
    encrypted_api_key: str | None,
    *,
    allow_saved_key: bool = True,
) -> str:
    """Resolve an explicit key, or a saved key only for an interactive UI session."""

    try:
        saved_provider_id = provider_id if allow_saved_key else None
        return credential_secrets.resolve_provider_api_key(saved_provider_id, encrypted_api_key)
    except Exception as exc:
        logger.warning(
            "external_provider.api_key_decrypt_failed",
            error_type = type(exc).__name__,
        )
        raise HTTPException(
            status_code = 400,
            detail = (
                "Failed to decrypt API key. The server public key may have changed — "
                "try refreshing the page."
            ),
        ) from exc

View on GitHub (pinned to 203007d190)

Solutions

  1. Refresh the page so the client re-fetches the public key from GET /api/providers/public-key, then re-enter and resubmit the API key.
  2. If it persists, verify the server's credential encryption key exists and is stable across restarts (get_or_create_credential_encryption_key should persist, not regenerate).
  3. Ensure all instances behind a load balancer share the same keypair/storage.
Defensive patterns

Strategy: retry

Validate before calling

const pub = await fetch("/api/providers/public-key").then(r => r.text());
if (pub !== cachedPublicKey) { cachedPublicKey = pub; /* re-encrypt the key before sending */ }

Try / catch

try { await submitKey(encrypted); } catch (e) { if (e.status === 400 && /decrypt/i.test(e.detail)) { await refreshPublicKey(); encrypted = await encryptKey(rawKey); return submitKey(encrypted); } throw e; }

Prevention

When it happens

Trigger: Submitting a provider form that has been open in a tab since before a server restart or key rotation; a cached SPA bundle holding an old public key; re-using an encrypted_api_key string captured from a previous session; sending malformed ciphertext.

Common situations: Server redeployed or database reset without updating the page; long-lived browser tabs; load-balanced instances where nodes disagree on the keypair; replaying recorded API traffic.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/f7faad9069e2a3f0. Report an issue: GitHub.