Vector store filter does not work with numeric-looking string attributes

Hi,

I am seeing unexpected behavior when filtering vector store files by an attribute whose value is a string containing only digits.

When a file is uploaded or attached to a vector store with the following numeric attribute:

{
  "document_id": 123456
}

the API subsequently returns the attribute value as:

{
  "document_id": 123456.0
}

Applying an equality filter with the corresponding numeric value works as expected:

{
  "type": "eq",
  "key": "document_id",
  "value": 123456
}

However, when the same file is uploaded or attached with the identifier stored as a string:

{
  "document_id": "123456"
}

and the search request uses an exact string equality filter:

{
  "type": "eq",
  "key": "document_id",
  "value": "123456"
}

the search returns no results, even though the attribute value used in the filter is identical to the value assigned when the file was added to the vector store.

What workaround can I use in my code?

Thank you!

You can ignore any bot-user above (before mods kill it) that pasted AI output minutes between postings to the forum and with seconds of “typing”, virtually ensuring little read time nor thought nor contextual understanding.


A workaround you can use is indeed to place alphabetic characters in your attribute metadata strings so that, in no case, can they be cast to a number type in transmission or be interpreted as a float.

Ultimately, we must discover, though, if this is your code, OpenAI’s offered SDK code, or the API itself that is causing the symptom, in order to arrive at a solution or to make a bug report.

This topic discusses vector store files, and the attributes that can be assigned to each file within the vector store object (not attributes or metadata on a file uploaded to the files endpoint, which would be wishful thinking never offered).

Then, there are two APIs that can perform vector store filtering based on a file attribute with a comparison filter such as eq for equals - either the standalone vector store search giving the results directly, or the Responses API, where the internal tool file_search also can accept filtering parameters. You didn’t clearly specify which. The latter is a bit harder to surface in an end-user product, so I will assume discrete file search API use (or that the symptom would occur on both).

To design a reproduction, I first must bypass SDK or language - making RESTful calls (apart from the file upload itself, which is multipart/form-data.)

I already have assistants/vector store/responses library methods, so can cut that down to a reproduction library, to then make further tests and see if we can get fault directly out of the API or have the wrong type retrieved despite transmitting it correctly.

There’s several steps here to set it up:

  • upload a file (or multiple)
  • create a vector store
  • add that file id (and polling because the API is lazy)
  • use attributes at creation time or with “update”
  • get them back to see damage
  • do some searching and see if we can filter or not filter

We can try both input types beyond “boolean”, and also try the first workaround suggestion.

{
  "document_id": "123456",
  "numeric_control": 654321,
  "string_control": "document-123456"
}

Seeing what comes back from list file or retrieve file gives a first answer.

First, the digit-only identifier is not being converted during transmission or storage. It remains a string at every boundary that the API exposes:

  • The exact request body contains "document_id":"123456".
  • The response from attaching the file returns "123456".
  • Retrieving the vector-store file after ingestion returns "123456".
  • An unfiltered vector-store search also returns "123456".
  • The filtered search request sends the comparison value as the JSON string "123456".

This reproduction also bypasses the OpenAI SDK and sends an explicitly serialized REST request. Therefore, there is no evidence here of Python, httpx, an SDK model, multipart upload handling, or the vector-store file creation endpoint changing the string into a number.

Having AI write a test harness and perform an analysis on a total of 17 billed searches, let’s turn it over to some AI analysis (that being Pro and slow, minimizes human thinking but maybe not human realtime).

The surrounding vector-store operations behaved normally: the vector store and files were created, both files completed ingestion, both appeared in an unfiltered search, the comparison controls ran successfully, and all server-side test assets were deleted afterward.

The client also transmitted the intended JSON types correctly. One file was attached with:

{
  "document_id": "123456"
}

and the other was attached with:

{
  "document_id": 123456
}

The exact serialized request bodies confirm that the first value was a JSON string and the second was a JSON number. Therefore, this is not Python, an OpenAI SDK, or the HTTP library changing the value before transmission.

There is, however, an important server-side type change. The file sent with numeric document_id: 123456 was already returned by the vector-store file attachment endpoint as the string "123456". Retrieving that vector-store file later also returned the value as a string. The first observable change therefore occurs no later than the vector-store file attachment/readback layer—not in the Files upload endpoint and not solely when search results are serialized.

That result suggests that the service normalizes values associated with an attribute key, possibly according to the type first established for that key. The internal implementation is not visible from the API, so that exact mechanism cannot be proven, but the client-side possibilities have been eliminated.

The comparison-filter tests then reproduce a clear, user-visible fault.

A string filter:

{
  "type": "eq",
  "key": "document_id",
  "value": "123456"
}

returned both the file supplied with the string value and the file supplied with the numeric value.

A numeric filter:

{
  "type": "eq",
  "key": "document_id",
  "value": 123456
}

also returned both files.

That happened identically in all three test rounds. The eq comparison is therefore treating the digit-only string "123456" and the number 123456 as equivalent instead of preserving the distinction between the two documented attribute-value types.

The controls rule out the filter simply being ignored:

  • "999999" and 999999 returned no files.
  • "id:string:123456" selected only the intended string file.
  • "id:number:123456" selected only the intended numeric file.
  • Numeric 123456 did not match either alphabetically prefixed identifier.

The comparison filter is active and does compare values. The unexpected behavior is specifically associated with digit-only strings that can be interpreted as numbers.

Workaround

Identifiers that must retain string-only equality semantics should contain at least one nonnumeric character. Apply the same canonical representation when storing and filtering:

{
  "document_id": "doc:123456"
}
{
  "type": "eq",
  "key": "document_id",
  "value": "doc:123456"
}

Bug report for OpenAI

Note: “I” written below means I ran a harness from gpt-5.6-sol “pro” and “I” is the AI writing about the analysis of it.

Title

eq filtering for vector-store file attributes treats digit-only strings and numbers as equivalent

Description

I reproduced unexpected cross-type equality behavior using direct REST requests to the standalone vector-store search endpoint:

POST /v1/vector_stores/{vector_store_id}/search

No OpenAI SDK was used. Request bodies were serialized directly, and the exact transmitted JSON was recorded.

I created one vector store and attached two different files with the same attribute key and digits but different JSON types:

{
  "document_id": "123456",
  "prefixed_document_id": "id:string:123456"
}
{
  "document_id": 123456,
  "prefixed_document_id": "id:number:123456"
}

The exact attachment request for the second file contained the JSON number:

{
  "document_id": 123456
}

However, the attachment response and subsequent vector-store-file retrieval exposed that value as the JSON string:

{
  "document_id": "123456"
}

I then searched using this string equality filter:

{
  "type": "eq",
  "key": "document_id",
  "value": "123456"
}

Expected: only the file originally supplied with the string attribute should be returned.

Actual: both the string-valued file and the number-valued file were returned.

I repeated the search with a numeric filter:

{
  "type": "eq",
  "key": "document_id",
  "value": 123456
}

Expected: only the file originally supplied with the numeric attribute should be returned.

Actual: both files were again returned.

The result was stable across three consecutive rounds.

Negative controls demonstrate that filtering was active rather than ignored:

{"type":"eq","key":"document_id","value":"999999"}

and:

{"type":"eq","key":"document_id","value":999999}

both returned no files.

Prefixed-string controls behaved correctly and selected their corresponding files individually:

{
  "type": "eq",
  "key": "prefixed_document_id",
  "value": "id:string:123456"
}
{
  "type": "eq",
  "key": "prefixed_document_id",
  "value": "id:number:123456"
}

A numeric filter value of 123456 did not match either prefixed identifier.

This indicates that digit-only strings and JSON numbers are being normalized into the same equality domain, despite string and number being exposed as separate supported types for vector-store file attributes and filter values.

This can cause distinct application identifiers to collide and can return files that should have been excluded by an exact typed comparison.

Please confirm whether this cross-type coercion is intended. If it is not intended, the vector-store attribute representation and eq comparison should preserve the distinction between JSON strings and JSON numbers. If coercion is intended, it should be documented explicitly because the current typed request schema does not make that behavior apparent.

Test time

August 4, 2026, approximately 06:25 GMT

Relevant request IDs

Attribute attachment and retrieval showing the numeric input exposed as a string:

Attachment: req_0a67556fc5e44a73bbf9cc3cd0ba1c74
Retrieval:  req_cefe550efe5841f5a2e57d9bbd59d7f0

Unfiltered search:

req_8beba3c957964275a582bdbb17fbd231

Typed equality matrix:

Round 1 string: req_53753c22ae764ab28384c123273c0152
Round 1 number: req_e76ca033566a4b2b87a29d0132a11a5c

Round 2 string: req_d9e0c747281f4d6eb74f456e7655c644
Round 2 number: req_8050cb36ea564279b4f9b987a3ecd004

Round 3 string: req_366b67149ffd43f3bde52924806fb91c
Round 3 number: req_d1610a6143e44929ba3984686e0d5e6c

Negative controls:

Wrong string: req_5c12bb3460b94f35806893fb8f1f22c0
Wrong number: req_b7e4b3a54f3c4cd0b33433c0e27fcbdb

Prefixed numeric probe:

req_808a062dd30a4fda8016a8ab8df0f6c9

All uploaded files and the vector store were deleted after the reproduction, though.


Python with RESTful library methods for vector stores

You can make use of the methods here, but they are cut down a bit from every parameter possible. The test harness is too much code to paste and would be exploratory API contract validation instead of immediately showing the issue.

"""Minimal reproduction for digit-only string attributes in vector-store search.

Python 3.11+:

    python -m pip install httpx
    export OPENAI_API_KEY="your-api-key"
    python openai_vector_store_digit_string_eq_repro.py

The script calls POST /vector_stores/{vector_store_id}/search directly. It does
not use the Responses API file_search tool. It creates one in-memory text file
and one vector store, then deletes both in a finally block.

Exit codes:
    0: The documented string/number distinction is preserved.
    1: A type or eq-filter inconsistency is reproduced.
    2: The run is inconclusive because setup or an API request failed.
"""

import asyncio
import json
import os
import sys
from io import BytesIO
from uuid import uuid4

import httpx


BASE_URL = "https://api.openai.com/v1"
TIMEOUT = httpx.Timeout(60.0, connect=10.0)
_MISSING = object()


def json_type(value: object) -> str:
    if value is _MISSING:
        return "missing"
    if value is None:
        return "null"
    if type(value) is str:
        return "string"
    if type(value) is bool:
        return "boolean"
    if type(value) in (int, float):
        return "number"
    return type(value).__name__


def probe(value: object) -> dict[str, object]:
    return {
        "value": "<missing>" if value is _MISSING else value,
        "json_type": json_type(value),
        "python_type": "missing" if value is _MISSING else type(value).__name__,
    }


def attributes(value: dict[str, object] | None) -> dict[str, object]:
    if value is None or value.get("attributes") is None:
        return {}
    result = value["attributes"]
    if type(result) is not dict:
        raise TypeError("The API returned attributes in a non-object form")
    return result


def attribute_report(value: dict[str, object]) -> dict[str, object]:
    return {
        key: probe(value.get(key, _MISSING))
        for key in ("document_id", "numeric_control", "string_control")
    }


def search_item(
    response: dict[str, object],
    file_id: str,
) -> dict[str, object] | None:
    data = response.get("data")
    if type(data) is not list:
        return None
    return next(
        (
            item
            for item in data
            if type(item) is dict and item.get("file_id") == file_id
        ),
        None,
    )


def file_ids(response: dict[str, object]) -> list[str]:
    data = response.get("data")
    if type(data) is not list:
        return []
    return [
        item["file_id"]
        for item in data
        if type(item) is dict and type(item.get("file_id")) is str
    ]


async def json_call(
    client: httpx.AsyncClient,
    method: str,
    path: str,
    body: dict[str, object] | None = None,
) -> tuple[dict[str, object], str | None, str]:
    """Send JSON without coercion and retain the exact request/response text."""
    request_text = None
    request_options: dict[str, object] = {}
    if body is not None:
        request_text = json.dumps(
            body,
            ensure_ascii=False,
            allow_nan=False,
            separators=(",", ":"),
        )
        request_options = {
            "content": request_text.encode(),
            "headers": {"Content-Type": "application/json"},
        }

    response = await client.request(
        method,
        f"{BASE_URL}{path}",
        **request_options,
    )
    response.raise_for_status()
    result = response.json()
    if type(result) is not dict:
        raise TypeError(f"Expected a JSON object from {response.request.url}")
    return result, request_text, response.text


async def upload_virtual_file(
    client: httpx.AsyncClient,
    marker: str,
) -> dict[str, object]:
    """Upload an in-memory file_234.txt.

    API form parameters:
        file: Required multipart file; this function supplies BytesIO content.
        purpose: assistants, batch, fine-tune, vision, user_data, or evals;
            this function sends user_data.
        expires_after: Optional created_at expiration policy; not exposed here.
    """
    text = (
        f"{marker}\n"
        "This document tests exact equality filtering on vector-store file "
        "attributes.\n"
        f"Search marker: {marker}\n"
    )
    with BytesIO(text.encode()) as file_handle:
        response = await client.post(
            f"{BASE_URL}/files",
            data={"purpose": "user_data"},
            files={"file": ("file_234.txt", file_handle, "text/plain")},
        )
    response.raise_for_status()
    result = response.json()
    if type(result) is not dict:
        raise TypeError("Expected a JSON object from POST /files")
    return result


async def create_vector_store(
    client: httpx.AsyncClient,
    name: str,
) -> dict[str, object]:
    """Create an empty vector store.

    API body parameters:
        name: Sent by this function.
        description, expires_after, file_ids, chunking_strategy, metadata:
            Supported by the endpoint but not exposed here.
    """
    result, _, _ = await json_call(
        client,
        "POST",
        "/vector_stores",
        {"name": name},
    )
    return result


async def attach_file(
    client: httpx.AsyncClient,
    vector_store_id: str,
    file_id: str,
    file_attributes: dict[str, str | int | float | bool],
) -> tuple[dict[str, object], str, str]:
    """Attach a file with typed attributes.

    API path parameters:
        vector_store_id: Required vector-store ID.
    API body parameters:
        file_id: Required uploaded File ID.
        attributes: Up to 16 string, number, or boolean values; sent unchanged.
        chunking_strategy: Optional; not exposed here.
    """
    result, request_text, response_text = await json_call(
        client,
        "POST",
        f"/vector_stores/{vector_store_id}/files",
        {"file_id": file_id, "attributes": file_attributes},
    )
    if request_text is None:
        raise RuntimeError("Attachment body was not serialized")
    return result, request_text, response_text


async def retrieve_vector_store_file(
    client: httpx.AsyncClient,
    vector_store_id: str,
    file_id: str,
) -> tuple[dict[str, object], str]:
    """Retrieve a vector-store file by vector_store_id and file_id."""
    result, _, response_text = await json_call(
        client,
        "GET",
        f"/vector_stores/{vector_store_id}/files/{file_id}",
    )
    return result, response_text


async def search_vector_store(
    client: httpx.AsyncClient,
    vector_store_id: str,
    query: str,
    filters: dict[str, object] | None = None,
) -> tuple[dict[str, object], str, str]:
    """Search a vector store.

    API body parameters:
        query: String or array of strings; this function sends a string.
        filters: ComparisonFilter or CompoundFilter; the harness uses only eq.
        max_num_results: 1 through 50; this function sends 10.
        ranking_options: Optional ranker and threshold; not exposed here.
        rewrite_query: Optional boolean; this function sends false.
    """
    body: dict[str, object] = {
        "query": query,
        "max_num_results": 10,
        "rewrite_query": False,
    }
    if filters is not None:
        body["filters"] = filters
    result, request_text, response_text = await json_call(
        client,
        "POST",
        f"/vector_stores/{vector_store_id}/search",
        body,
    )
    if request_text is None:
        raise RuntimeError("Search body was not serialized")
    return result, request_text, response_text


async def delete_file(
    client: httpx.AsyncClient,
    file_id: str,
) -> dict[str, object]:
    """DELETE /files/{file_id}; deletion also removes vector-store attachments."""
    result, _, _ = await json_call(client, "DELETE", f"/files/{file_id}")
    return result


async def delete_vector_store(
    client: httpx.AsyncClient,
    vector_store_id: str,
) -> dict[str, object]:
    """DELETE /vector_stores/{vector_store_id}."""
    result, _, _ = await json_call(
        client,
        "DELETE",
        f"/vector_stores/{vector_store_id}",
    )
    return result


async def eq_test(
    client: httpx.AsyncClient,
    vector_store_id: str,
    file_id: str,
    query: str,
    name: str,
    key: str,
    value: str | int,
    expected: bool,
) -> dict[str, object]:
    result, request_text, response_text = await search_vector_store(
        client,
        vector_store_id,
        query,
        {"type": "eq", "key": key, "value": value},
    )
    request_filter = json.loads(request_text)["filters"]
    actual = search_item(result, file_id) is not None
    return {
        "name": name,
        "filter_value": probe(request_filter["value"]),
        "expected_match": expected,
        "actual_match": actual,
        "passed": actual == expected,
        "returned_file_ids": file_ids(result),
        "request_json": request_text,
        "response_json": response_text,
    }


def diagnosis(
    boundary_types: dict[str, str],
    unfiltered: bool,
    results: dict[str, dict[str, object]],
) -> dict[str, str | bool]:
    if boundary_types["request"] != "string":
        return {
            "conclusive": True,
            "status": "client_side_conversion",
            "explanation": "document_id was no longer a string in the HTTP body.",
        }

    for boundary in ("attachment", "retrieval", "search_result"):
        if boundary_types[boundary] == "number":
            return {
                "conclusive": True,
                "status": "server_side_type_change",
                "explanation": (
                    "document_id was sent as a string and first appeared as a "
                    f"number at the {boundary} boundary."
                ),
            }

    if not unfiltered:
        return {
            "conclusive": False,
            "status": "inconclusive_unfiltered_search",
            "explanation": "The file was not returned without a filter.",
        }

    numeric_control = bool(results["numeric_control"]["actual_match"])
    string_control = bool(results["string_control"]["actual_match"])
    string_target = bool(results["string_target"]["actual_match"])
    numeric_probe = bool(results["numeric_probe"]["actual_match"])

    if not numeric_control or not string_control:
        return {
            "conclusive": False,
            "status": "inconclusive_positive_control_failure",
            "explanation": "A numeric or ordinary-string eq control failed.",
        }
    if not string_target and numeric_probe:
        return {
            "conclusive": True,
            "status": "numeric_coercion_in_eq_filter_reproduced",
            "explanation": (
                "Readback remains string, but string eq misses and numeric eq on "
                "the same key and digits matches. The inconsistency is in the "
                "search attribute index or eq comparison path."
            ),
        }
    if not string_target and not numeric_probe:
        return {
            "conclusive": True,
            "status": "digit_string_eq_miss_without_numeric_match",
            "explanation": (
                "The string eq miss is reproduced, but numeric eq also misses; "
                "conversion to number is not proven."
            ),
        }
    if string_target and numeric_probe:
        return {
            "conclusive": True,
            "status": "type_coercing_eq_comparison",
            "explanation": "Both string and numeric eq filters match the string.",
        }
    return {
        "conclusive": True,
        "status": "not_reproduced",
        "explanation": "String eq matches and numeric eq does not.",
    }


def error_report(error: Exception) -> dict[str, object]:
    result: dict[str, object] = {
        "type": type(error).__name__,
        "message": str(error),
    }
    if isinstance(error, httpx.HTTPStatusError):
        result.update(
            status_code=error.response.status_code,
            request_url=str(error.request.url),
            response_json=error.response.text,
        )
    return result


async def cleanup(
    client: httpx.AsyncClient,
    file_id: str | None,
    vector_store_id: str | None,
) -> dict[str, object]:
    result: dict[str, object] = {}
    if file_id is not None:
        try:
            result["file"] = await delete_file(client, file_id)
        except Exception as error:
            result["file_error"] = error_report(error)
    if vector_store_id is not None:
        try:
            result["vector_store"] = await delete_vector_store(
                client,
                vector_store_id,
            )
        except Exception as error:
            result["vector_store_error"] = error_report(error)
    return result

Hello,

Thank you for the detailed investigation and reproduction. It helped clarify that digit-only strings are not necessarily converted during transmission or storage.

I would like to provide additional information because our observed behavior differs from your reproduction.

Environment and API path

We are using:

  • Python
  • OpenAI Python library version 2.53.0
  • The standalone Vector Store Search API, not the Responses API file_search tool
  • vector_stores.file_batches.create to attach files with per-file attributes

Before attachment, document_id is explicitly converted to a string:

attributes = {
    "document_id": str(document_id),
}

Files are attached using:

openai_client.vector_stores.file_batches.create(
    vector_store_id=vector_store_id,
    files=[
        {
            "file_id": file_id,
            "attributes": attributes,
        }
    ],
)

Search is performed using:

direct_response = openai_client.vector_stores.search(
    **search_kwargs,
    filters=document_id_filter,
)

The filter is:

document_id_filter = {
    "type": "eq",
    "key": "document_id",
    "value": str(document_id),
}

Additional checks performed

We inspected the affected vector-store files and verified that document_id is stored and returned as a string.

For a failing document_id:

  • An eq filter using str(document_id) returns no results.
  • An eq filter using int(document_id) also returns no results.
  • Running the same search without the document_id filter returns the expected file. This is how we isolated the filter as the cause.
  • The file was attached using the same vector_stores.file_batches.create path as files for which filtering works.

Observed pattern

The issue appears to be vector-store-specific:

  • The same filter implementation works in other vector stores.
  • For the failing values, neither the string nor integer representation matches, even though file inspection shows that the stored attribute exists as a string.

Therefore, our case differs from the reproduction where both string and numeric filter values matched. In our affected cases, neither representation matches the stored attribute.

Possible correlation with deleted files

One potentially relevant difference is that the affected vector store has a history of file deletions. We have not established any causal relationship, but could deletion or subsequent index maintenance affect the consistency of the attribute-filter index?

Workaround question

Adding an alphabetic prefix would require modifying the attributes of approximately 10,000 existing vector-store files, which would be a significant migration.

Is there a retrieval-only workaround that can reliably match the existing digit-only string attributes without modifying their stored values—for example, another comparison operator or filter representation?

Thank you.

I didn’t test file_batches for type mutation, where the SDK is powered by /vector_stores/{vector_store_id}/file_batches - the schema and text is the same for attributes, so probably pulled from the same $def in the API specification. You don’t see fault with sending strings and pulling back strings from individual files ( “retrieve vector store file” - {base}/vector_stores/{vector_store_id}/files/{file_id}) so you’ve done the check I would do.

You’re sending strings, so as a workaround, one has to assume “symptomatic strings” are what you are filtering on, your document_id with a number string having been sent having not my over-matching, but your “no matches and we don’t know why”.

I happen to have the API reference as markdown open, having fed it into AI, so can go right over to supply you all the filter methods and we can ponder them:

  • filters: optional ComparisonFilter or CompoundFilter

    A filter to apply based on file attributes.

    • ComparisonFilter object { key, type, value }

      A filter used to compare a specified attribute key to a given value using a defined comparison operation.

      • key: string

        The key to compare against the value.

      • type: "eq" or "ne" or "gt" or 5 more

        Specifies the comparison operator: eq, ne, gt, gte, lt, lte, in, nin.

        • eq: equals
        • ne: not equal
        • gt: greater than
        • gte: greater than or equal
        • lt: less than
        • lte: less than or equal
        • in: in
        • nin: not in

Then you have the input(s) of attributes to evaluate with the operator:

  • value: string or number or boolean or array of string or number

    The value to compare against the attribute key; supports string, number, or boolean types.

    • string

    • number

    • boolean

    • array of string or number

      • string

      • number

So: you’ve got some number type tests possible if your vector store is currently returning numbers or acting as though numbers are the type or a query would succeed on numbers - as we don’t expect but do get in my repro. It seems that our combined tests point to “weirdness” and not necessarily a clear answer of what you now have in your vector store file IDs as anything that could work.

If there was over-matching, then another operator that would return something for strings that can also act as numbers would be gte - greater than or equal, or just gt. Those obviously work on number inputs - and number queries are what gets converted to a float. Floats of course are weird and gte becomes untrustworthy. In a *combined request with CompoundFilter and using "and", you could constrain a numeric search to be greater than index-1 and less than index+1 if eq is not matching due to precision and a number value search might work otherwise. That gives a non-inclusive range that your number must be centered within.

Fix the vector store? API calls to amend or update attributes are free, unlike a complete re-embedding. You could let a job run on existing vector store files, adding a different field name and giving the workaround string.


That might be lots of useless talk, though…

Here’s the highest probability of the latest symptom, as I see it - completely outside of types: No matches at all might be the extreme number of files in a vector store. The API has several places where a return is capped at 10000 items, with or without pagination. Yes, the same “approximately” you report. It is possible that this high file count simply breaks the filtering or that the input to the search is truncated to just some of the files.

Thus, you might have file deletes as a way to make a vector store work again by reducing the file count - or you might have the API’s often-faulty delete that leaves stuff behind - amplifying the count over real files and exacerbating the file count truncation or query breakage symptom.

Otherwise - just a corrupted vector store.

Hey @pmitrovic

Thanks for the detailed follow-up. You’ve already checked both string and numeric filters, and I understand why updating around 10,000 files would be a significant change.

One additional check would be to try in with a single string value, keeping the rest of your search request unchanged:

{"type": "in", "key": "document_id", "value": ["123456"]}

Replace "123456" with an affected identifier. The Vector Store Search API reference supports this format, though I can’t confirm it will resolve the missing matches.

If you’re still seeing the issue, please open a new thread linking back to this one, with a sanitized example and a recent request ID and timestamp with timezone. You can also share those details through OpenAI Support, using your existing support request if you already have one or by creating a new support request.

~ Ankit