openai/openai-python · error · RuntimeError

Unexpected JSON data type, {type(json_data)}, cannot merge w

Error message

Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`

What it means

The OpenAI Python SDK raises this ValueError when await client.vector_stores.file_batches.retrieve() (async) is called with a falsy vector_store_id. It is a client-side pre-flight check so the GET /vector_stores/{vector_store_id}/file_batches/{batch_id} request is never sent with an invalid path segment.

Source

Thrown at src/openai/_base_client.py:538

        *,
        retries_taken: int = 0,
    ) -> httpx2.Request:
        # Request bodies, files, URLs, and custom options can contain private data.
        log.debug(
            "Building HTTP request: method=%s retries_taken=%i",
            get_http_method_for_logging(options.method),
            retries_taken,
        )
        kwargs: dict[str, Any] = {}

        json_data = options.json_data
        if options.extra_json is not None:
            if json_data is None:
                json_data = cast(Body, options.extra_json)
            elif is_mapping(json_data):
                json_data = _merge_mappings(json_data, options.extra_json)
            else:
                raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`")

        headers = self._build_headers(options, retries_taken=retries_taken)
        params = _merge_mappings({**self._auth_query(options.security), **self.default_query}, options.params)
        content_type = headers.get("Content-Type")
        files = options.files

        # If the given Content-Type header is multipart/form-data then it
        # has to be removed so that httpx can generate the header with
        # additional information for us as it has to be in this form
        # for the server to be able to correctly parse the request:
        # multipart/form-data; boundary=---abc--
        if content_type is not None and content_type.startswith("multipart/form-data"):
            if "boundary" not in content_type:
                # only remove the header if the boundary hasn't been explicitly set
                # as the caller doesn't want httpx to come up with their own boundary
                headers.pop("Content-Type")

            # As we are now sending multipart/form-data instead of application/json

View on GitHub (pinned to 9917c6e28e)

Solutions

  1. Fetch and pass the actual 'vs_...' store identifier; verify the variable is populated before the retrieve call.
  2. Use dict.get() carefully: distinguish a missing key from an empty value and re-fetch the store ID if absent.
  3. In polling loops, validate both IDs once before entering the loop.

Example fix

# before
batch = await client.vector_stores.file_batches.retrieve(vector_store_id=job["store"], batch_id=job["batch"])  # job["store"] == ""

# after
vs = job.get("store") or (await client.vector_stores.create(name="docs")).id
batch = await client.vector_stores.file_batches.retrieve(vector_store_id=vs, batch_id=job["batch"])
Defensive patterns

Strategy: validation

Validate before calling

vs = job.get("vector_store_id")
if not vs:
    raise ValueError("job record is missing vector_store_id")
batch = await client.vector_stores.file_batches.retrieve(vector_store_id=vs, batch_id=job["batch_id"])

Type guard

def is_valid_vector_store_id(v: object) -> bool:
    return isinstance(v, str) and v.startswith("vs_") and len(v) > 3

Prevention

When it happens

Trigger: Awaiting retrieve(vector_store_id='', batch_id='vsfb_...') or vector_store_id=None on the async resource, e.g. when polling batch status with a store ID that came back empty.

Common situations: Polling loops that read IDs from dictionaries with missing keys, deserialized job records with absent store fields, or tests using placeholder empty strings.

Related errors


AI-assisted analysis of openai/openai-python@9917c6e28e (2026-08-28). Data as JSON: /api/errors/8e7f0a8f04e70187. Report an issue: GitHub.