Files & vector stores
A vector store is a searchable index over your documents: upload files,
attach them to a store, and the API chunks, embeds, and indexes them. Agents
search the store with the hosted file_search tool; you can also query it
directly. The surface mirrors the OpenAI Files and Vector Stores APIs, so an
OpenAI client library pointed at the API works unchanged.
Ingest and query embeddings run on your project's OpenAI model key (bring your own or Ingram-hosted) and are metered like any model call. Each store picks its own embedder — including one you host.
Upload files
Files are uploaded once and reusable across stores. Multipart, like OpenAI:
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/files \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-F purpose=assistants \
-F file=@handbook.pdf
The response is the OpenAI File object (id, bytes, filename, purpose).
PDFs and text formats (Markdown, HTML, JSON, CSV, source code, …) are
indexable; other types upload fine but fail indexing with
unsupported_file. Indexing reads a PDF's text layer — it does not OCR.
A scanned or image-only PDF (no text layer) fails indexing with
no_text_layer; OCR it before uploading. GET /v1/files lists uploads, GET /v1/files/{fid} and
…/content fetch one back, DELETE /v1/files/{fid} removes it everywhere —
including from any vector stores it was attached to.
To see what the indexer made of a file — the chunk text the model actually
retrieves, not the bytes you uploaded — read
GET /v1/vector_stores/{vsId}/files/{fid}/content. A file that failed indexing
has no chunks and reads as an empty page.
Files that arrive inline in a conversation (a screenshot or PDF in a chat
turn) live on the same surface: retrievable by id for auditability, but not
listed — GET /v1/files enumerates uploads only.
Create a store and attach files
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/vector_stores \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{ "name": "Support KB", "file_ids": ["file_abc"] }'
Attaching is asynchronous: the vector store file starts in_progress and
moves to completed (searchable) or failed (last_error says why —
unsupported_file, no_text_layer (a scanned/image-only PDF — OCR it first),
invalid_file, server_error). A bulk import can partially succeed, so check
each file's status/last_error (or the vector_store.file.failed events)
rather than assuming the whole batch landed. The store object's
file_counts tracks the fleet; its status is in_progress while anything
is still indexing. The indexing queue is durable: files queue in
in_progress and every one ends completed or failed — a redeploy or
crash mid-indexing resumes where it left off. Attach more later via POST …/{vsId}/files
({"file_id": …}), or in bulk via POST …/{vsId}/file_batches
({"file_ids": […]}; cancel with POST …/file_batches/{batchId}/cancel).
Chunking follows OpenAI: auto (800-token chunks, 400 overlap) or
{"type": "static", "static": {"max_chunk_size_tokens": …, "chunk_overlap_tokens": …}}
per file.
Choose the embedder. embedding_model names the model that indexes and
searches the store. Omit it for the platform default
(openai.text-embedding-3-small); name any model your project's OpenAI model key can
reach, of at most 1536 dimensions:
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/vector_stores \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{ "name": "Support KB", "embedding_model": "text-embedding-3-large" }'
Set a base_url on that model key and the store embeds against your
endpoint — an EU-hosted OpenAI-compatible embedder keeps the corpus and every
query inside the region. The store object returns embedding_model.
The choice is frozen at create: no modify accepts it, because a different model
means re-embedding the whole corpus. Create one store per embedder and attach
the files again. Create verifies the model by embedding one probe string
(metered like any call), so an unreachable model, a bad id, or one wider than
1536 dimensions fails right there with a 422 rather than failing every file
you attach.
Attributes and filters. Each file can carry up to 16 typed attributes
({"region": "EU", "year": 2026}) which every search can filter with
OpenAI's grammar — comparisons (eq, ne, gt, gte, lt, lte, in,
nin) composed with and/or:
{ "type": "and", "filters": [
{ "type": "eq", "key": "region", "value": "EU" },
{ "type": "gte", "key": "year", "value": 2026 }
] }
Search directly
POST /v1/vector_stores/{vsId}/search is semantic search with no model in the
loop — use the store as your retrieval layer and generate anywhere:
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/vector_stores/vs_abc/search \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{ "query": "what is the refund window?", "max_num_results": 5 }'
Results come back as scored chunks (file_id, filename, score,
content), OpenAI's search_results.page shape. filters and
ranking_options.score_threshold narrow them.
Give an agent the knowledge base
Two config fields on the agent (or per-smith override):
enabled_hosted_toolsincludes"file_search"— the tool itself.vector_store_idslists the stores it may search.
Every channel the smith runs on — chat, Slack, Discord, email, schedules —
gets the tool automatically. A store is tenant-wide by default; create it
with a smith_id to scope it to one smith (that smith's token sees only
tenant-wide stores and its own).
On the OpenAI-compatible surface, declare it per request instead — the run
emits a file_search_call output item, and
include: ["file_search_call.results"] returns the retrieved chunks. (One
divergence from OpenAI today: file_search cannot be combined with client
function tools in the same request.)
# Authorization: smith token (safe for your backend per end-user)
curl https://api.cloud.ingram.tech/v1/responses \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{
"input": "What does the handbook say about refunds?",
"tools": [{ "type": "file_search", "vector_store_ids": ["vs_abc"] }],
"include": ["file_search_call.results"]
}'
In the console
Build → Vector stores lists your stores; a store's page uploads files,
shows per-file indexing status, and has a try-out search that runs the same
/search the agent's tool uses. Attach stores to an agent from the agent's
Knowledge section (visible once file_search is enabled).
Build → Files lists the files you've uploaded over the API (GET /v1/files) —
filename, purpose, size, and status — the ones reusable by file_id on runs or as
vector-store sources. Inline bytes sent on a single run aren't uploads and aren't
listed here.
Lifecycle events
Indexing emits events: vector_store.file.completed and
vector_store.file.failed per file, vector_store.created / .deleted for
the store itself.