Vector Stores

aimock implements the 14 OpenAI Vector Stores endpoints listed below in memory. An SDK can upload a file, attach it to a store, poll it to completed, and call POST /v1/vector_stores/{id}/search directly without touching api.openai.com. Progression is deterministic and driven by retrieves.

This mock does not execute the hosted file_search tool in Responses or Assistants. Vector-store contents do not automatically produce model responses. Responses requests need separate matching fixtures for local mock responses. In default mock mode, an unmatched Responses request returns 404 with no_fixture_match. Strict mode returns 503 instead, even when an upstream is configured. With strict mode disabled, record/proxy mode forwards unmatched requests to the configured upstream.

Endpoints

Method Path Purpose
POST /v1/vector_stores Create a store, optionally with file_ids
GET /v1/vector_stores List stores, newest first (limit, order, after, before)
GET /v1/vector_stores/{id} Retrieve a store (refreshes its derived status)
POST /v1/vector_stores/{id} Modify name, metadata, expires_after
DELETE /v1/vector_stores/{id} Delete a store and its files and batches
POST /v1/vector_stores/{id}/files Attach a file_id from the Files store
GET /v1/vector_stores/{id}/files List attached files (filter, pagination)
GET /v1/vector_stores/{id}/files/{file_id} Retrieve a file, advancing it one poll step
DELETE /v1/vector_stores/{id}/files/{file_id} Detach a file
POST /v1/vector_stores/{id}/file_batches Create a file batch from file_ids
GET /v1/vector_stores/{id}/file_batches/{batch_id} Retrieve a batch, advancing it one poll step
POST /v1/vector_stores/{id}/file_batches/{batch_id}/cancel Cancel a non-terminal batch
GET /v1/vector_stores/{id}/file_batches/{batch_id}/files List a batch's files (pagination)
POST /v1/vector_stores/{id}/search Deterministic search over completed files

Deterministic Progression

A new file starts at in_progress. The first retrieve after attaching shows in_progress; the second lands the outcome (completed by default) and the file is stable from then on. File batches follow the same two-poll rule. A store's own status is derived: in_progress while any file or batch is still live, completed once everything settles, and expired once expires_at passes.

Status ladder text
attach    -> in_progress
retrieve  -> in_progress
retrieve  -> completed | failed | cancelled   (stable afterwards)

batch     -> in_progress
retrieve  -> in_progress
retrieve  -> completed | failed | cancelled
cancel    -> cancelling -> cancelled          (400 once terminal)

Failure Injection

Send X-AIMock-Vector-Outcome: completed | failed | cancelled on POST …/files or POST …/file_batches to choose where the second poll lands (default completed; anything else is a 400). A failed file carries a last_error: { code: "mock_ingest_failed", … } payload so error rendering paths are testable.

Files Must Exist First

Attaching a file_id the Files store has never seen is a 404 naming the file, so a suite that forgets the upload step fails loudly instead of indexing a ghost. usage_bytes is the real byte length from the Files store. Upload with purpose: "assistants" (or any accepted Files purpose) before attaching.

Upload → store → poll → search ts
const file = await openai.files.create({
  file: fs.createReadStream("./docs.txt"),
  purpose: "assistants",
});
const store = await openai.vectorStores.create({ name: "rag" });
await openai.vectorStores.files.create(store.id, { file_id: file.id });
await openai.vectorStores.files.retrieve(store.id, file.id); // in_progress
await openai.vectorStores.files.retrieve(store.id, file.id); // completed
const hits = await fetch(`${base}/v1/vector_stores/${store.id}/search`, {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ query: "pricing", max_num_results: 5 }),
}).then((r) => r.json());

Search

Search is deterministic, not semantic: only completed files are searched, ranking is a stable hash of query + file_id so the same query always orders the same way, and every hit carries its file id plus a canned text snippet quoting the query. query accepts a non-empty string or a non-empty array of non-empty strings. Array entries are joined with a newline in request order for ranking and snippets; a single-entry array ranks identically to its string. The response search_query echoes the original string or array. max_num_results is an integer in 1–50 (default 10); ranking_options.score_threshold is a number in 0–1. An empty store answers 200 with data: []. Direct search supports tests of top-k, thresholds, and empty results. It does not measure semantic relevance.

File attachment and batch creation accept attributes: up to 16 pairs with keys of at most 64 characters and string values of at most 512 characters, numbers, or booleans. Batch attributes apply to each newly attached file. Omitted or null attributes are returned as null; an empty object stays empty. Search results include the file's attributes.

Search filters accept comparisons { type: "eq", key: "category", value: "guide" } using eq, ne, gt, gte, lt, or lte, and nested groups { type: "and", filters: [...] } or { type: "or", filters: [...] }. Filters run before ranking and result limits. Comparisons require an existing key and matching value types; missing keys do not match, including with ne. Ordering uses numeric, lexicographic string, or false-before-true boolean order. Malformed attributes or filters return 400.

Validation

State & Journal

Stores live in memory for the life of the process. POST /__aimock/reset, LLMock.reset() and clearVectorStoreStore() all clear them. Every request is journaled exactly once with service: "vector-stores" and runs through the chaos gate — see Chaos Testing. Prometheus labels are normalized (/v1/vector_stores/{id}, /v1/vector_stores/{id}/files/{fileId}, /v1/vector_stores/{id}/file_batches/{batchId}[/cancel|/files], /v1/vector_stores/{id}/search), so caller-controlled ids cannot inflate cardinality — see Prometheus Metrics.