RAG · MANAGED VECTOR STORES

Managed RAG behind the file_search API.

Every team that ships search-over-documents builds the same four things, in the same order, usually by accident. A parser for whatever customers upload. A vector index that stays isolated per customer. Some way for support to answer 'why was search bad on Tuesday' without a data engineer. And a billing pipeline that ties embedding spend back to the tenant who caused it, which nobody budgets for and everybody eventually needs. We've been on that side of the table. Ringside ships those four as one product, behind an API you already know.

The wire format is OpenAI's. POST /v1/vector_stores, then call file_search inside an Assistants run. The pieces we built differently from the hosted alternatives are pricing, file-type coverage, model choice and how much of the retrieval pipeline you can see.

OpenAI-compatible · 25+ MIME types · Wallet credits don't expire

Four calls from zero to a working search

# 1. Create a vector store
curl https://api.fightclub.pro/v1/vector_stores \
  -H "Authorization: Bearer $FC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"acme-handbook","embedding_model":"text-embedding-3-small"}'
# => { "id": "vs_abc", ... }

# 2. Upload a file (any of 25+ MIME types: pdf, docx, pptx, png, mp3, ...)
curl https://api.fightclub.pro/v1/files \
  -H "Authorization: Bearer $FC_API_KEY" \
  -F purpose=attachments -F file=@handbook.pdf
# => { "id": "file_xyz", ... }

# 3. Attach the file to the store
curl https://api.fightclub.pro/v1/vector_stores/vs_abc/files \
  -H "Authorization: Bearer $FC_API_KEY" \
  -d '{"file_id":"file_xyz"}'

# 4. Query from inside an Assistants run
curl https://api.fightclub.pro/v1/threads/$THREAD/runs \
  -H "Authorization: Bearer $FC_API_KEY" \
  -d '{
    "assistant_id":"asst_...",
    "tools":[{"type":"file_search","file_search":{"vector_store_ids":["vs_abc"]}}]
  }'

Past the first four calls, the full /v1/vector_stores surface mirrors OpenAI's: GET to list stores or files, PATCH/DELETE on a store, POST /file_batches for up to 500 file_ids in one call, cancel/retry on stuck ingests, a query log + stats endpoint, and embedding-model migrations with 7-day index retention. To switch on graph retrieval, set graphrag_enabled: true on the store at create or via PATCH; file_search then returns a facts array next to the chunk citations. To seal a store at rest, set encryption: "managed" at create (see Encrypt a store at rest). Full reference at /docs/vector_stores.

What you get

Open file-type ingestion

25+ MIME types in v1, no extra parsing tier. PDF and Word, but also PowerPoint, images with text in them, audio you want transcribed, CSV tables. Anything a modern vision or audio model can read, we route through the right parser and bill you the parse tokens we actually used. No per-file flat fee.

Retrieval that follows the links

Plain vector search finds the chunk closest to the question and stops there. When the answer is spread across files (one doc names a part's supplier, another says that supplier moved cities), the nearest-chunk match misses it. As your files ingest, we also build a graph of the entities in them and how they connect, so a query can walk those links and pull facts from documents the text match alone would never surface. It runs underneath the same file_search call. Turn it on per store, and if the graph has nothing relevant you get normal vector results and pay nothing extra.

Per-customer isolation by default

Each vector store maps to one tenant in our index. Cross-customer reads return 404, not 403, so a buggy assistant in a multi-tenant app can't probe for the existence of another tenant's data. Delete a customer and the whole tenant goes with them.

Seal a store at rest

Some data can't sit in a managed index as plaintext. Create the store with encryption set to managed and every chunk's text and its embedding vector is sealed under a per-store key before it's written, so a stolen database is ciphertext. Sealing takes text-based files today (text, Markdown, JSON, XML, YAML), so export the document first; binary PDFs and DOCX go on a plaintext store. The file_search call doesn't change either way. The section below spells out exactly what it does and doesn't protect.

Token + per-GB-day storage

Tokens for parse, embed, query and re-embed. Per-GB-day for what we hold: vector index, raw files, and (opt-in) the GraphRAG graph. That's it. The first 1 GB-day per store per day is free on the vector index and the graph. Most managed vector products bill four or five opaque line items.

Swap embedding models without re-uploading

Pick a different embedding model in the dashboard. We re-embed in the background and swap to the new model atomically so your queries never hit a half-populated index. The previous index is retained for seven days. The parse step is cached, so you pay embedding tokens only.

Retrieval you can actually inspect

Every query writes a row to the query log with the question text, top-K scores, returned file IDs, latency and embedding tokens. The stats tab on the dashboard rolls those up into queries-per-day, p95 latency, empty-result rate and a daily cost line. When a customer says 'your search is bad', you can show them why.

Credits don't expire

Top up the wallet and the balance stays available until you spend it. Hosted alternatives often expire unused credits after 12 months. We don't.

Encrypt a store at rest

Sometimes the documents are the kind you can't put in a third-party index in the clear, like a case file or a signed agreement. Add one field at create and the store gets its own random key, wrapped under our server key. Every chunk's text and embedding vector is sealed with AES-256-GCM before it's written, and a search decrypts only the chunks it hands back. Dump the database and all you get is ciphertext. The file_search call is identical, so nothing in your application changes. Sealed ingest is text-only today (text, Markdown, plain text and formats like JSON, XML and YAML), so export the document to text or Markdown first; a binary PDF or DOCX goes on a plaintext store.

# Same flow as above, one extra field. Seal the store at create:
curl https://api.fightclub.pro/v1/vector_stores \
  -H "Authorization: Bearer $FC_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "acme-contracts",
    "embedding_model": "text-embedding-3-small",
    "encryption": "managed"
  }'
# => { "id": "vs_...", "encryption": "managed", "vector_sealing": "full" }

# Upload, attach and file_search exactly as before. Chunk text and embedding
# vectors are sealed (AES-256-GCM) under a per-store key before they're stored;
# a query decrypts only the chunks it returns. Opt down with
# "vector_sealing": "source" to keep vectors in plaintext for speed (they're
# invertible, which is why full is the default).

Be precise about what this buys. managed encrypts your data at rest under a key we hold, so a stolen disk or a leaked database backup is unreadable. It isn't zero-knowledge. To embed and answer a query we hold the plaintext in memory briefly, and matched chunks come back decrypted over TLS. When you need a key we can't read, create the store with encryption: "byok" and a passphrase you present per session: we store only a wrapped copy, never the passphrase, so a locked byok store is ciphertext even to us. Lock and unlock with POST /v1/vector_stores/:id/lock and /unlock.

By default the vectors are sealed too (vector_sealing: "full"), so nothing readable survives a database dump. You can opt down to vector_sealing: "source" and keep the vectors in plaintext for faster ranking. Raw embeddings can be partially reversed back to the text that produced them, so that mode guards the files and not their meaning. Full is the default for that reason.

GraphRAG seals the same way: turn on graphrag_enabled and the entity labels and relationships are sealed under your key, with only the facts a query projects decrypted. Retrieval keeps each store's vectors in a warm in-memory index, so search stays fast at the per-customer scale a sealed store holds. Ingest is self-healing: a sweeper re-drives any file a restart left mid-flight, so nothing needs re-attaching by hand. Sealed stores take text-based files only for now (text, Markdown, plain text, JSON, XML, YAML), capped at 25 MB; a sealed PDF or DOCX fails ingest, so binary documents belong on a plaintext store, whose worker path streams and parses them.

For a very large sealed store you don't want held decrypted in memory, set vector_index: "ivf". We cluster the vectors and a query decrypts only the nearest clusters, so a big corpus stays cheap to search without keeping it all in RAM. That mode stores plaintext cluster centroids, which reveal how your documents group at rest (not their content), so it's opt-in. When even that grouping can't leak, the answer is the confidential deployment below.

Two honest limits. The shape of a sealed graph (which entities connect, not their names) stays visible at rest, and a key we hold means we can technically decrypt while serving you. If your threat model allows neither, the answer is a confidential deployment: bring-your-own-RAG, where the sealed engine runs inside your own confidential-compute environment (your SEV-SNP / TDX hardware, your key) so we never touch the data, or a customer-managed-key (cloud KMS) variant we host. Both are available for regulated deployments on request.

Pricing in two lines

Tokens
Pass-through model rates with a small platform margin. Parse, embed, query and re-embed all bill the same way.
Storage
$0.05 per GB-day on the vector index, $0.04 per GB-day on file storage, $0.08 per GB-day on the (opt-in) GraphRAG graph. First 1 GB-day per store per day is free on the vector index and the graph.
Wallet credits never expire. Per-customer top-ups and budgets work the same as the rest of Ringside.