{"record":{"id":"f7faad9069e2a3f0","repo":"unslothai/unsloth","slug":"failed-to-decrypt-api-key-the-server-public-key-m","errorCode":null,"errorMessage":"Failed to decrypt API key. The server public key may have changed — try refreshing the page.","messagePattern":"Failed to decrypt API key\\. The server public key may have changed — try refreshing the page\\.","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"studio/backend/routes/provider_credentials.py","lineNumber":56,"sourceCode":"\n\ndef resolve_provider_api_key_or_400(\n    provider_id: str | None,\n    encrypted_api_key: str | None,\n    *,\n    allow_saved_key: bool = True,\n) -> str:\n    \"\"\"Resolve an explicit key, or a saved key only for an interactive UI session.\"\"\"\n\n    try:\n        saved_provider_id = provider_id if allow_saved_key else None\n        return credential_secrets.resolve_provider_api_key(saved_provider_id, encrypted_api_key)\n    except Exception as exc:\n        logger.warning(\n            \"external_provider.api_key_decrypt_failed\",\n            error_type = type(exc).__name__,\n        )\n        raise HTTPException(\n            status_code = 400,\n            detail = (\n                \"Failed to decrypt API key. The server public key may have changed — \"\n                \"try refreshing the page.\"\n            ),\n        ) from exc\n","sourceCodeStart":38,"sourceCodeEnd":63,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/routes/provider_credentials.py#L38-L63","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","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).","Ensure all instances behind a load balancer share the same keypair/storage."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const pub = await fetch(\"/api/providers/public-key\").then(r => r.text());\nif (pub !== cachedPublicKey) { cachedPublicKey = pub; /* re-encrypt the key before sending */ }","typeGuard":null,"tryCatchPattern":"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; }","preventionTips":["Fetch the server public key immediately before each encryption, not at page load only.","Never persist encrypted_api_key ciphertext for later reuse; keep only the raw key in memory.","Ensure the server's encryption key persists across restarts."],"tags":["encryption","rsa","api-key","provider-credentials"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}