invoke-ai/InvokeAI · error · RuntimeError

Failed to load api keys file {api_keys_file_path}: expected

Error message

Failed to load api keys file {api_keys_file_path}: expected a mapping

What it means

load_external_api_keys parses the dedicated external provider API keys YAML file and requires its top level to be a mapping (dict). If yaml.safe_load returns a non-dict value (list, scalar), it raises this RuntimeError because provider fields cannot be looked up by name.

Source

Thrown at invokeai/app/services/config/config_default.py:651

        )
        return config
    except Exception as e:
        raise RuntimeError(f"Failed to load config file {config_path}: {e}") from e


def load_external_api_keys(api_keys_file_path: Path) -> dict[str, str]:
    """Load external provider config (API keys and base URLs) from a dedicated YAML file."""
    if not api_keys_file_path.exists():
        return {}

    with open(api_keys_file_path, "rt", encoding=locale.getpreferredencoding()) as file:
        loaded_api_keys: Any = yaml.safe_load(file)

    if loaded_api_keys is None:
        return {}

    if not isinstance(loaded_api_keys, dict):
        raise RuntimeError(f"Failed to load api keys file {api_keys_file_path}: expected a mapping")

    parsed_api_keys: dict[str, str] = {}
    for field_name in EXTERNAL_PROVIDER_CONFIG_FIELDS:
        value = loaded_api_keys.get(field_name)
        if value is None:
            continue
        if not isinstance(value, str):
            raise RuntimeError(
                f"Failed to load api keys file {api_keys_file_path}: value for '{field_name}' must be a string"
            )
        stripped_value = value.strip()
        if stripped_value:
            parsed_api_keys[field_name] = stripped_value

    return parsed_api_keys


@lru_cache(maxsize=1)

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Restructure the file so the top level is a mapping of EXTERNAL_PROVIDER_CONFIG_FIELDS names to string values
  2. Ensure each entry is `field_name: value` with the value on the same line, not a nested list
  3. Quote values that YAML could misinterpret as other types (e.g. keys starting with numbers)
  4. If the file is junk and not needed, delete it (a missing file is tolerated and returns {})

Example fix

// before (api_keys.yml)
- openai: sk-abc
- replicate: r8_xxx
// after

openai: sk-abc
replicate: r8_xxx
Defensive patterns

Strategy: validation

Validate before calling

import yaml
from pathlib import Path
def api_keys_file_ok(p: Path) -> bool:
    if not p.exists():
        return True  # missing file is tolerated
    data = yaml.safe_load(p.read_text())
    return data is None or isinstance(data, dict)

Type guard

def is_mapping(v: object) -> bool:
    return isinstance(v, dict)

Try / catch

try:
    keys = load_external_api_keys(path)
except RuntimeError as e:
    logger.error(f"bad api keys file: {e}")
    keys = {}

Prevention

When it happens

Trigger: Calling load_external_api_keys (via get_config or _apply_external_provider_update) when api_keys_file_path exists but its YAML root is a list, string, number, or boolean.

Common situations: api_keys.yml contains only a bare key like `sk-abc123` (no top-level mapping), or a list of key/value pairs in the wrong shape, or the file was written by a script using an unexpected format.

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/3ba7dad134abec51. Report an issue: GitHub.