invoke-ai/InvokeAI · error · ExternalProviderRequestError

Gemini response payload was not a JSON object

Error message

Gemini response payload was not a JSON object

What it means

ExternalProviderRequestError raised when Gemini responds 200 OK but response.json() yields something other than a JSON object (dict) — for example a JSON array, string, number, or null. The provider expects the standard GenerateContentResponse object shape and refuses to parse anything else. This almost always indicates the configured endpoint/proxy is not the real Gemini API or an intermediary mangled the response.

Source

Thrown at invokeai/app/services/external_generation/providers/gemini.py:126

            params={"key": api_key},
            json=payload,
            timeout=120,
        )

        if not response.ok:
            if response.status_code == 429:
                retry_after = _parse_retry_after(response.headers.get("retry-after"))
                raise ExternalProviderRateLimitError(
                    f"Gemini rate limit exceeded. {f'Retry after {retry_after:.0f}s.' if retry_after else 'Please try again later.'}",
                    retry_after=retry_after,
                )
            raise ExternalProviderRequestError(
                f"Gemini request failed with status {response.status_code} for model '{model_id}': {response.text}"
            )

        data = response.json()
        if not isinstance(data, dict):
            raise ExternalProviderRequestError("Gemini response payload was not a JSON object")
        images: list[ExternalGeneratedImage] = []
        text_parts: list[str] = []
        finish_messages: list[str] = []
        candidates = data.get("candidates")
        if not isinstance(candidates, list):
            raise ExternalProviderRequestError("Gemini response payload missing candidates")
        for candidate in candidates:
            if not isinstance(candidate, dict):
                continue
            finish_message = candidate.get("finishMessage")
            finish_reason = candidate.get("finishReason")
            if isinstance(finish_message, str):
                finish_messages.append(finish_message)
            elif isinstance(finish_reason, str):
                finish_messages.append(f"Finish reason: {finish_reason}")
            for part in _iter_response_parts(candidate):
                inline_data = part.get("inline_data") or part.get("inlineData")
                if isinstance(inline_data, dict):

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Log/print the raw response body before parsing to see what was actually returned.
  2. Unset any custom external_gemini_base_url so the official https://generativelanguage.googleapis.com endpoint is used.
  3. If using a proxy, configure it to pass through Google's response unchanged rather than re-wrapping it.
  4. Fix test mocks to return a dict-shaped GenerateContentResponse.

Example fix

// before: assuming a proxy speaks Gemini's schema
base_url = "https://my-proxy.internal/v1"
// after: use the official endpoint (or verify the proxy passthrough)
base_url = None  # provider defaults to https://generativelanguage.googleapis.com/v1beta
Defensive patterns

Strategy: type-guard

Type guard

def is_gemini_response(data: object) -> bool:
    return (
        isinstance(data, dict)
        and isinstance(data.get("candidates"), list)
        and len(data["candidates"]) > 0
    )
# use on the raw JSON before trusting the envelope:
# data = response.json(); if not is_gemini_response(data): fail fast

Try / catch

try:
    result = provider.generate(request)
except ExternalProviderRequestError as e:
    if "not a JSON object" in str(e):
        logger.error("Non-Gemini response shape; check external_gemini_base_url/proxy")
    raise

Prevention

When it happens

Trigger: data = response.json() returns a non-dict: a custom external_gemini_base_url (proxy/gateway) returning an error array or plain value with status 200; an auth-gateway returning JSON like {"list": ...} shapes; a misconfigured mock server in tests; a captive portal or JSON-API wrapper returning a top-level list.

Common situations: Pointing external_gemini_base_url at an OpenAI-compatible proxy that returns a different schema; corporate proxy intercepting responses; upgrading Gemini behind a translation layer that changed the envelope; unit-test fake returning the wrong top-level type.

Related errors


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