> 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 Python SDK.

# RAG (Retrieval-Augmented Generation)

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

## Quick upload

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

```python
from meshapi import MeshAPI

client = MeshAPI(base_url="https://api.meshapi.ai", token="rsk_...")

with open("handbook.pdf", "rb") as f:
    upload = client.rag.upload_file(
        file_name="handbook.pdf",
        mime_type="application/pdf",
        content=f.read(),
    )

print("File ID:", upload.file_id)
```

### Async

```python
from meshapi import AsyncMeshAPI

async with AsyncMeshAPI(base_url="https://api.meshapi.ai", token="rsk_...") as client:
    with open("handbook.pdf", "rb") as f:
        upload = await client.rag.upload_file(
            file_name="handbook.pdf",
            mime_type="application/pdf",
            content=f.read(),
        )
```

## Two-step upload

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

```python
from meshapi._types import InitUploadRequest
import httpx

upload = client.rag.init_upload(
    InitUploadRequest(file_name="handbook.pdf", mime_type="application/pdf", embed=False)
)

# PUT bytes directly to the signed URL (no auth header needed)
httpx.put(upload.signed_url, content=pdf_bytes, headers={"Content-Type": "application/pdf"}).raise_for_status()
```

## Trigger embedding

```python
from meshapi._types import BulkEmbedRequest

resp = client.rag.embed(BulkEmbedRequest(file_ids=[upload.file_id]))
for r in resp.results:
    print(r.file_id, r.embedding_status)
```

## Poll until ready

```python
import time

while True:
    status = client.rag.get(upload.file_id)
    if status.embedding_status == "ready":
        break
    if status.embedding_status == "failed":
        raise RuntimeError(f"Embedding failed: {status.last_error_code}")
    time.sleep(3)
```

## Search

```python
from meshapi._types import SearchRequest

results = client.rag.search(
    SearchRequest(
        query="What is the refund policy?",
        top_k=5,
        file_ids=[upload.file_id],  # omit to search all files
    )
)

for r in results.results:
    print(f"[{r.score:.3f}] {r.text}")
```

### Search options

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

## List files

```python
page = client.rag.list(limit=50)
print(f"{page.total} total files")
for f in page.files:
    print(f.file_id, f.upload_status, f.embedding_status)
```

## RAG chat

Combine search results with a chat completion:

```python
from meshapi import ChatCompletionParams, ChatMessage

results = client.rag.search(SearchRequest(query="What is the refund policy?", top_k=3))
context = "\n\n".join(r.text for r in results.results)

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