> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developers.meshapi.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developers.meshapi.ai/_mcp/server.

# RAG (Files & Search)

> Upload files, embed them, and run vector search with the Node.js SDK.

# RAG (Retrieval-Augmented Generation)

Upload documents, generate embeddings, and query them semantically — all through `client.rag`.

## Quick upload

The `uploadFile` convenience method handles both the init call and the PUT to the signed URL in one step:

```ts
import { MeshAPI } from "meshapi-node-sdk";
import fs from "fs";

const client = new MeshAPI({ baseUrl: "https://api.meshapi.ai", token: "rsk_..." });

const upload = await client.rag.uploadFile({
  file_name: "handbook.pdf",
  mime_type: "application/pdf",
  content: fs.readFileSync("handbook.pdf"),
});

console.log("File ID:", upload.file_id);
```

## Two-step upload

Use `initUpload` when you want to control the PUT yourself:

```ts
const upload = await client.rag.initUpload({
  file_name: "handbook.pdf",
  mime_type: "application/pdf",
  embed: false,
});

// PUT bytes directly to the signed URL (no auth header needed)
await fetch(upload.signed_url, {
  method: "PUT",
  body: fs.readFileSync("handbook.pdf"),
  headers: { "Content-Type": "application/pdf" },
});
```

## Trigger embedding

```ts
const resp = await client.rag.embed({ file_ids: [upload.file_id] });
for (const r of resp.results) {
  console.log(r.file_id, r.embedding_status);
}
```

## Poll until ready

```ts
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));

while (true) {
  const status = await client.rag.get(upload.file_id);
  if (status.embedding_status === "ready") break;
  if (status.embedding_status === "failed") {
    throw new Error(`Embedding failed: ${status.last_error_code}`);
  }
  await sleep(3_000);
}
```

## Search

```ts
const results = await client.rag.search({
  query: "What is the refund policy?",
  top_k: 5,
  file_ids: [upload.file_id], // omit to search all files
});

for (const r of results.results) {
  console.log(`[${r.score.toFixed(3)}] ${r.text}`);
}
```

### Search options

| Field       | Type      | Notes                                       |
| ----------- | --------- | ------------------------------------------- |
| `query`     | string    | Plain-language question                     |
| `top_k`     | number    | Results to return (1–50, default 5)         |
| `file_ids`  | string\[] | Restrict to specific files                  |
| `filter`    | object    | Match on metadata key-value pairs           |
| `date_from` | number    | Unix timestamp — only chunks created after  |
| `date_to`   | number    | Unix timestamp — only chunks created before |

## List files

```ts
const list = await client.rag.list({ limit: 50 });
console.log(`${list.total} total files`);
for (const f of list.files) {
  console.log(f.file_id, f.upload_status, f.embedding_status);
}
```

## RAG chat

Combine search results with a chat completion:

```ts
const results = await client.rag.search({
  query: "What is the refund policy?",
  top_k: 3,
});
const context = results.results.map((r) => r.text).join("\n\n");

const reply = await client.chat.completions.create({
  model: "openai/gpt-4o-mini",
  messages: [
    { role: "system", content: `Answer using only the context below.\n\n${context}` },
    { role: "user",   content: "What is the refund policy?" },
  ],
});
console.log(reply.choices[0]?.message.content);
```