BerriAI/litellm · error · OCIError
oci_key must be a string containing the PEM private key cont
Error message
oci_key must be a string containing the PEM private key content. Got type: {type(oci_key).__name__} What it means
In the manual signing path, if oci_key is truthy it must be a str containing PEM content; any other type (bytes, dict, path-like, list) raises OCIError(400) naming the offending type. The check exists because serialized keys (e.g. read as bytes from a vault) would otherwise fail later with a confusing serialization error.
Source
Thrown at litellm/llms/oci/common_utils.py:307
}
signed_header_names: Final = [
"date",
"(request-target)",
"host",
"content-length",
"content-type",
"x-content-sha256",
]
signing_string: Final = build_signature_string(method, path, headers_to_sign, signed_header_names)
_require_cryptography()
# Resolve the private key — prefer inline PEM content over file path
oci_key_content: str | None = None
if oci_key:
if not isinstance(oci_key, str):
raise OCIError(
status_code=400,
message=(
f"oci_key must be a string containing the PEM private key content. "
f"Got type: {type(oci_key).__name__}"
),
)
oci_key_content = oci_key.replace("\\n", "\n").replace("\r\n", "\n")
private_key: Final = (
load_private_key_from_str(oci_key_content)
if oci_key_content
else load_private_key_from_file(oci_key_file)
if oci_key_file
else None
)
if private_key is None:
raise OCIError(View on GitHub (pinned to 6c2dcb801b)
Solutions
- Decode bytes to str: oci_key=key_bytes.decode('utf-8').
- If the value is a path, use oci_key_file (or OCI_KEY_FILE) instead of oci_key.
- If the secret is JSON-wrapped, extract the PEM field: json.loads(secret)['pem'].
- Confirm the value starts with '-----BEGIN' and ends with '-----END ... KEY-----'.
Example fix
# before
optional_params["oci_key"] = secret_manager.get("oci_key") # bytes → OCIError 400
# after
key = secret_manager.get("oci_key")
if isinstance(key, bytes):
key = key.decode("utf-8")
optional_params["oci_key"] = key Defensive patterns
Strategy: type-guard
Validate before calling
key = optional_params.get("oci_key")
if isinstance(key, bytes):
key = key.decode("utf-8")
assert key is None or (isinstance(key, str) and "-----BEGIN" in key), "oci_key must be PEM string content" Type guard
from typing import Any
def is_pem_key_string(v: Any) -> bool:
return isinstance(v, str) and v.lstrip().startswith("-----BEGIN") Try / catch
from litellm.llms.oci.common_utils import OCIError
try:
litellm.completion(model="oci/...", messages=m)
except OCIError as e:
if e.status_code == 400 and "oci_key must be a string" in str(e):
optional_params["oci_key"] = optional_params["oci_key"].decode("utf-8")
raise Prevention
- Decode secret-manager bytes to str before assigning oci_key.
- Use oci_key_file for paths — never put a path into oci_key.
- Unit-test the credential normalization step of your integration.
When it happens
Trigger: Passing oci_key=b'-----BEGIN PRIVATE KEY-----...' (bytes from AWS Secrets Manager / Vault SDKs), an OCI config dict, or accidentally passing the key *file path* as oci_key instead of its contents.
Common situations: Secret managers returning bytes; JSON-encoded secrets passed as parsed dicts; confusing oci_key (inline PEM string) with oci_key_file (path) and passing a path to oci_key.
Related errors
- Private key file is empty: {file_path}
- OIDC token could not be retrieved from secret manager.
- {e.response.text}
- Chunk is not a string: {chunk}
- The provided private key is not an RSA key, which is require
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/78d9a90a389647dc.
Report an issue: GitHub.