iflytek/astron-agent · error · BusinessException

MODEL_APIKEY_LOAD_ERROR

MODEL_APIKEY_LOAD_ERROR

Error message

BusinessException(ResponseEnum.MODEL_APIKEY_LOAD_ERROR)

What it means

MODEL_APIKEY_LOAD_ERROR is thrown by ModelService.decryptApiKey when the stored model API key cannot be decrypted. The service loads an RSA private key from the model secret key config and decrypts the Base64-encoded API key; any exception in loadPrivateKey or decryptByPrivateKeyBase64 (malformed PEM, wrong key, corrupt ciphertext) is wrapped in this BusinessException.

Solutions

  1. Verify the modelSecretKey config value contains a complete, valid RSA private key PEM (correct BEGIN/END markers, no truncation).
  2. Re-save/re-encrypt the model API key so it was encrypted with the current private key, then retry validation.
  3. Check server logs for the underlying 'Decrypt API Key failed' stack trace to distinguish key-load vs decrypt failure.
  4. Ensure RSAUtil's expected key format (PKCS#8 vs PKCS#1) matches what is stored; convert the key if needed.
  5. Confirm the apiKey field stored for the model is valid Base64 with no whitespace or characters lost in transit.

Example fix

// before: storing a key encrypted with an old/rotated keypair
apiKey = encryptWithOldPublicKey(rawKey); // decrypt fails at runtime
// after: re-encrypt with the current keypair before saving
apiKey = RSAUtil.encryptByPublicKeyBase64(rawKey, RSAUtil.loadPublicKey(currentPublicKey));
Defensive patterns

Strategy: validation

Validate before calling

boolean keyUsable = modelSecretKey != null && modelSecretKey.contains("BEGIN") && modelSecretKey.contains("END") && isBase64(apiKey);
if (!keyUsable) { fixCredentialsBeforeValidate(); }

Type guard

boolean isValidPem(String s) { return s != null && s.strip().startsWith("-----BEGIN") && s.strip().endsWith("-----") && !s.isBlank(); }

Try / catch

try { validateModel(req); } catch (BusinessException e) { if ("MODEL_APIKEY_LOAD_ERROR".equals(e.getCode())) { reencryptAndResaveApiKey(); } throw e; }

Prevention

When it happens

Trigger: validateModel calls decryptApiKey and the RSA private key stored in the modelSecretKey config value is invalid, not valid PEM, or the apiKey ciphertext was not encrypted with (or cannot be decrypted by) that key; Base64 decoding of the apiKey also fails.

Common situations: Secret-key config row missing or truncated after a DB migration/import; API key encrypted with a different keypair after rotating keys; copy-pasted key lost padding or newlines; environment (modelSecretKey) pointing to a stale value.

Related errors


AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12). Data as JSON: /api/errors/5d503f61d63f15d5. Report an issue: GitHub.

Appendix: source

Thrown at console/backend/toolkit/src/main/java/com/iflytek/astron/console/toolkit/service/model/ModelService.java:194

        }
    }


    private String decryptApiKey(String apiKey) {
        ConfigInfo modelSecretKey = configInfoMapper.selectOne(Wrappers.<ConfigInfo>lambdaQuery()
                .eq(ConfigInfo::getCategory, "MODEL_SECRET_KEY")
                .eq(ConfigInfo::getCode, "private_key")
                .eq(ConfigInfo::getIsValid, 1));
        if (modelSecretKey == null) {
            throw new BusinessException(ResponseEnum.MODEL_API_KEY_NOT_FOUND);
        }

        try {
            RSAPrivateKey privateKey = RSAUtil.loadPrivateKey(modelSecretKey.getValue());
            return RSAUtil.decryptByPrivateKeyBase64(apiKey, privateKey);
        } catch (Exception e) {
            log.error("Decrypt API Key failed", e);
            throw new BusinessException(ResponseEnum.MODEL_APIKEY_LOAD_ERROR);
        }
    }

    private Map<String, Object> buildValidationPayload(String modelDomain, String provider) {
        if (PROVIDER_GOOGLE.equals(provider)) {
            Map<String, Object> textPart = new HashMap<>();
            textPart.put("text", "Hello!");

            Map<String, Object> content = new HashMap<>();
            content.put("role", "user");
            content.put("parts", Collections.singletonList(textPart));

            Map<String, Object> generationConfig = new HashMap<>();
            generationConfig.put("maxOutputTokens", 16);

            Map<String, Object> payload = new HashMap<>();
            payload.put("contents", Collections.singletonList(content));
            payload.put("generationConfig", generationConfig);

View on GitHub (pinned to 5e758547a8)