
# Vector search & RAG

One retrieval story, three moving parts: a **vector baton** (a prepaid, durable index with a frozen shape), a **query** op (top-k similarity search — pass a raw text query and it's embedded for you), and **`rag/answer`** (embed the question, retrieve the top-k chunks, and answer from *only* those chunks with source ids). You can use the pieces separately or let `rag/answer` run the whole loop under one charge.

## 1. Create a vector baton

A vector baton is a normal prepaid [baton](/docs/batons) with the `vector` preset and a **frozen index shape** — its `dims` and distance `metric` are set at create and can't change afterward.

```bash
curl -X POST https://api.relaystation.ai/v1/baton \
  -H 'Authorization: Bearer rs_live_<key>' \
  -H 'Idempotency-Key: mk-vector-20260707' \
  -H 'Content-Type: application/json' \
  -d '{ "preset": "vector", "tier": "standard",
        "vector": { "dims": 1024, "metric": "cosine" },
        "name": "docs-index" }'
```

- `vector.dims` must be one of the allowlisted sizes (`256` / `512` / `1024` — the [embed](/docs/embeddings) model's output sizes). Defaults to `1024`.
- `vector.metric` is `cosine` (default) or `euclidean`.
- Storage is **prepaid at create** — it's a baton, so its capacity, lifetime, and egress are baton economics (see [Baton pricing](/pricing)). Pick a `tier` (or a `customShape`) that fits your index.

The response carries the baton `id` you use for the vector routes below.

## 2. Write vectors

`POST /v1/baton/{id}/vectors` batch-writes vectors into the index. Each item is `{ id, vector, metadata? }`; a batch is 1–1000 items, each vector must match the baton's frozen `dims`.

```bash
curl -X POST https://api.relaystation.ai/v1/baton/<id>/vectors \
  -H 'Authorization: Bearer rs_live_<key>' \
  -H 'Idempotency-Key: put-batch-20260707' \
  -H 'Content-Type: application/json' \
  -d '{ "vectors": [
        { "id": "chunk-1", "vector": [0.01, -0.02, ...], "metadata": { "text": "…", "source": "doc-A" } },
        { "id": "chunk-2", "vector": [0.03,  0.04, ...], "metadata": { "text": "…", "source": "doc-B" } }
      ] }'
```

Returns `{ batonId, written, bytesCharged, dims }`. Writes **draw the baton's prepaid size quota** (that storage was already paid for at create); the write op itself is metered per batch. Store the chunk text in `metadata` so retrieval — and `rag/answer` — can read it back.

## 3. Query top-k

`POST /v1/baton/{id}/vectors/query` runs a top-k similarity search. Pass a `vector` you embedded yourself, **or** pass raw `text` and it's embedded for you before the search.

```bash
curl -X POST https://api.relaystation.ai/v1/baton/<id>/vectors/query \
  -H 'Authorization: Bearer rs_live_<key>' \
  -H 'Idempotency-Key: query-20260707' \
  -H 'Content-Type: application/json' \
  -d '{ "text": "how do refunds work?", "topK": 5 }'
```

Returns `{ batonId, matches: [...], embedded? }` — the nearest neighbors (with their ids, scores, and metadata), and an `embedded` marker when the query text was vectorized for you. The query is a flat per-call charge; when you pass `text`, the embedding units to vectorize it are added. Response bytes draw the baton's egress budget, exactly like a content read.

## 4. Answer, grounded

`POST /v1/rag/answer` runs the full retrieval-augmented loop in **one call**: it embeds the `question`, queries the vector baton's index top-k, and answers from **only** the retrieved chunks — returning the answer together with the **source ids** it grounded on.

| Field | Type | Notes |
|---|---|---|
| `batonId` | string, required | the vector baton to retrieve from |
| `question` | string, required | the question (1–65536 chars) |
| `topK` | integer | how many chunks to retrieve (1–1000) |
| `tier` | enum | `budget` \| `value` \| `best` — the answer model's spend ceiling |

```bash
curl -X POST https://api.relaystation.ai/v1/rag/answer \
  -H 'Authorization: Bearer rs_live_<key>' \
  -H 'Idempotency-Key: rag-20260707' \
  -H 'Content-Type: application/json' \
  -d '{ "batonId": "<id>", "question": "What is the refund window?", "topK": 5, "tier": "value" }'
```

Returns `{ answer, sources, usage }` — the grounded answer, the `sources` (the chunk ids it used), and a `usage` object whose `chargedMicros` is never above the authorized `ceilingMicros`. The retrieved chunks are fed to the model as **untrusted data** — the instruction to answer only from them is ours, not the chunks'.

## Billing

- **Storage** (the vector baton) — prepaid at create; baton economics (capacity / lifetime / egress). See [Baton pricing](/pricing).
- **Writes** (`vectors`) — metered per batch, and draw the baton's prepaid size quota.
- **Query** (`vectors/query`) — a flat per-query charge, plus embedding units when you pass raw `text`; response bytes draw egress.
- **`rag/answer`** — **settle-at-actual (cost-plus)**: you authorize a ceiling (embedding + retrieval + the answer model's tier ceiling) and pay the actual, itemized on the receipt, never above the ceiling.

No hard prices here — see [Pricing](/pricing) for the current rates, or send an unauthenticated `POST` to read the exact price from the `402` challenge. Every billable call needs an `Idempotency-Key`; a same-key retry returns the cached result without re-charging.

## MCP tools

Callable over MCP at `https://api.relaystation.ai/mcp`: `vector_put` and `vector_query` (under the baton family), and `rag_answer`. Same auth, same pricing as the HTTP routes.

## Next

[Embeddings](/docs/embeddings) · [Batons](/docs/batons) · [LLM tasks](/docs/llm-tasks) · [Quickstart](/docs/quickstart) · [x402 wire format](/docs/x402) · [API reference](/api-reference)
