> ## Documentation Index
> Fetch the complete documentation index at: https://developers.meshapi.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Files & RAG

> Upload your documents, search them semantically, and ground AI answers in your own content — all through the Mesh API.

RAG lets you upload files, automatically convert them into searchable chunks, and then query them with plain-language questions. The API returns the most relevant passages, which you can feed directly into a chat completion to get answers grounded in your own content.

The full flow is three steps:

1. **Upload** a file and get a `file_id`
2. **Wait** for embeddings to finish processing
3. **Search** with a natural-language query

```http theme={null}
POST https://api.meshapi.ai/v1/files        # upload
POST https://api.meshapi.ai/v1/files/embed  # (re-)trigger embeddings
POST https://api.meshapi.ai/v1/files/search # semantic search
```

***

## Step 1 — Upload a file

Call `POST /v1/files` to register the file. You get back a `file_id` and a short-lived `signed_url` — use the signed URL to PUT the actual bytes directly to storage.

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    # 1a. Register the file and get an upload URL
    INIT=$(curl -s https://api.meshapi.ai/v1/files \
      -H "Authorization: Bearer <YOUR_RSK_KEY>" \
      -H "Content-Type: application/json" \
      -d '{
        "file_name": "handbook.pdf",
        "mime_type": "application/pdf",
        "embed": true
      }')

    FILE_ID=$(echo $INIT | jq -r '.file_id')
    SIGNED_URL=$(echo $INIT | jq -r '.signed_url')

    # 1b. Upload the file bytes to the signed URL
    curl -X PUT "$SIGNED_URL" \
      -H "Content-Type: application/pdf" \
      --data-binary @handbook.pdf

    echo "File ID: $FILE_ID"
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import httpx

    client = httpx.Client(headers={"Authorization": "Bearer rsk_..."})

    # Register the file
    init = client.post(
        "https://api.meshapi.ai/v1/files",
        json={
            "file_name": "handbook.pdf",
            "mime_type": "application/pdf",
            "embed": True,
        },
    ).json()

    file_id = init["file_id"]
    signed_url = init["signed_url"]

    # Upload bytes to the signed URL (no auth header needed here)
    with open("handbook.pdf", "rb") as f:
        httpx.put(signed_url, content=f.read(), headers={"Content-Type": "application/pdf"})

    print(f"File ID: {file_id}")
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    import fs from "fs";

    const init = await fetch("https://api.meshapi.ai/v1/files", {
      method: "POST",
      headers: {
        Authorization: "Bearer <YOUR_RSK_KEY>",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        file_name: "handbook.pdf",
        mime_type: "application/pdf",
        embed: true,
      }),
    }).then((r) => r.json());

    const { file_id, signed_url } = init;

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

    console.log("File ID:", file_id);
    ```
  </Tab>
</Tabs>

### Init upload fields

| Field       | Type    | Notes                                                                         |
| ----------- | ------- | ----------------------------------------------------------------------------- |
| `file_name` | string  | Name of the file (used for display and file-type detection)                   |
| `mime_type` | string  | MIME type, e.g. `application/pdf`, `text/plain`, `text/markdown`              |
| `embed`     | boolean | Default `true` — automatically triggers embedding after upload                |
| `metadata`  | object  | Optional key-value pairs attached to every chunk (useful for filtering later) |

### Init upload response

```json theme={null}
{
  "file_id": "file_abc123",
  "signed_url": "https://storage.example.com/...",
  "expires_at": "2026-05-26T12:30:00Z"
}
```

<Warning>
  The `signed_url` expires in a few minutes. PUT your file bytes immediately after receiving it.
</Warning>

***

## Step 2 — Wait for embeddings

After the PUT completes, the API automatically chunks and embeds your file (because `embed: true`). You can poll `GET /v1/files/{file_id}` to watch progress.

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl https://api.meshapi.ai/v1/files/$FILE_ID \
      -H "Authorization: Bearer <YOUR_RSK_KEY>"
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import time

    while True:
        status = client.get(f"https://api.meshapi.ai/v1/files/{file_id}").json()
        print(status["upload_status"], status["embedding_status"])
        if status["embedding_status"] in ("ready", "failed"):
            break
        time.sleep(3)
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    const poll = async () => {
      while (true) {
        const status = await fetch(
          `https://api.meshapi.ai/v1/files/${file_id}`,
          { headers: { Authorization: "Bearer <YOUR_RSK_KEY>" } }
        ).then((r) => r.json());

        console.log(status.upload_status, status.embedding_status);
        if (["ready", "failed"].includes(status.embedding_status)) break;
        await new Promise((r) => setTimeout(r, 3000));
      }
    };
    await poll();
    ```
  </Tab>
</Tabs>

### Status fields

| Field              | Values                                                    | Meaning                         |
| ------------------ | --------------------------------------------------------- | ------------------------------- |
| `upload_status`    | `pending` → `ready`                                       | File bytes have been received   |
| `embedding_status` | `pending` → `queued` → `processing` → `ready` \| `failed` | Chunking and embedding progress |

Once `embedding_status` is `ready`, the file is ready to search.

***

## Step 3 — Search your files

Send a natural-language query to `POST /v1/files/search`. The API converts your query into a vector, finds the closest chunks, and returns the raw text with relevance scores.

<Tabs>
  <Tab title="curl">
    ```bash theme={null}
    curl https://api.meshapi.ai/v1/files/search \
      -H "Authorization: Bearer <YOUR_RSK_KEY>" \
      -H "Content-Type: application/json" \
      -d '{
        "query": "What is the refund policy?",
        "top_k": 5
      }'
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    results = client.post(
        "https://api.meshapi.ai/v1/files/search",
        json={
            "query": "What is the refund policy?",
            "top_k": 5,
        },
    ).json()

    for r in results["results"]:
        print(f"[{r['score']:.3f}] {r['text'][:200]}")
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    const results = await fetch("https://api.meshapi.ai/v1/files/search", {
      method: "POST",
      headers: {
        Authorization: "Bearer <YOUR_RSK_KEY>",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        query: "What is the refund policy?",
        top_k: 5,
      }),
    }).then((r) => r.json());

    for (const r of results.results) {
      console.log(`[${r.score.toFixed(3)}] ${r.text.slice(0, 200)}`);
    }
    ```
  </Tab>
</Tabs>

### Search fields

| Field       | Type      | Notes                                                            |
| ----------- | --------- | ---------------------------------------------------------------- |
| `query`     | string    | Plain-language question or phrase                                |
| `top_k`     | integer   | Number of chunks to return (1–50, default 5)                     |
| `file_ids`  | string\[] | Restrict search to specific files; omit to search all your files |
| `filter`    | object    | Match chunks by metadata key-value pairs (exact match)           |
| `date_from` | integer   | Unix timestamp — only return chunks created after this time      |
| `date_to`   | integer   | Unix timestamp — only return chunks created before this time     |

### Search response

```json theme={null}
{
  "results": [
    {
      "score": 0.912,
      "text": "Refunds are processed within 5–7 business days...",
      "parent_text": "Section 4: Returns and Refunds\n\nRefunds are processed...",
      "file_id": "file_abc123",
      "file_name": "handbook.pdf",
      "chunk_index": 14,
      "metadata": {}
    }
  ]
}
```

***

## End-to-end: RAG chat

Combine search results with a chat completion to answer questions from your documents.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import httpx, json

    API = "https://api.meshapi.ai"
    HEADERS = {"Authorization": "Bearer rsk_...", "Content-Type": "application/json"}
    client = httpx.Client(headers=HEADERS)

    question = "What is the refund policy?"

    # 1. Retrieve relevant chunks
    search = client.post(f"{API}/v1/files/search", json={
        "query": question,
        "top_k": 3,
    }).json()

    context = "\n\n".join(r["text"] for r in search["results"])

    # 2. Ask the model
    chat = client.post(f"{API}/v1/chat/completions", json={
        "model": "openai/gpt-4o-mini",
        "messages": [
            {
                "role": "system",
                "content": f"Answer using only the context below.\n\n{context}",
            },
            {"role": "user", "content": question},
        ],
    }).json()

    print(chat["choices"][0]["message"]["content"])
    ```
  </Tab>

  <Tab title="Node.js">
    ```ts theme={null}
    const API = "https://api.meshapi.ai";
    const headers = {
      Authorization: "Bearer <YOUR_RSK_KEY>",
      "Content-Type": "application/json",
    };

    const question = "What is the refund policy?";

    // 1. Retrieve relevant chunks
    const search = await fetch(`${API}/v1/files/search`, {
      method: "POST",
      headers,
      body: JSON.stringify({ query: question, top_k: 3 }),
    }).then((r) => r.json());

    const context = search.results.map((r: any) => r.text).join("\n\n");

    // 2. Ask the model
    const chat = await fetch(`${API}/v1/chat/completions`, {
      method: "POST",
      headers,
      body: JSON.stringify({
        model: "openai/gpt-4o-mini",
        messages: [
          {
            role: "system",
            content: `Answer using only the context below.\n\n${context}`,
          },
          { role: "user", content: question },
        ],
      }),
    }).then((r) => r.json());

    console.log(chat.choices[0].message.content);
    ```
  </Tab>
</Tabs>

***

## Filtering by metadata

Tag files at upload time and filter at search time — useful when you have documents from different departments, clients, or time periods.

```bash theme={null}
# Upload with metadata
curl https://api.meshapi.ai/v1/files \
  -H "Authorization: Bearer <YOUR_RSK_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "file_name": "q4-report.pdf",
    "mime_type": "application/pdf",
    "metadata": { "department": "finance", "quarter": "Q4-2025" }
  }'

# Search only finance docs
curl https://api.meshapi.ai/v1/files/search \
  -H "Authorization: Bearer <YOUR_RSK_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "What were the operating expenses?",
    "filter": { "department": "finance" }
  }'
```

***

## Re-triggering embeddings

If `embed` was set to `false` at upload time, or if embedding failed, you can kick it off manually:

```bash theme={null}
curl https://api.meshapi.ai/v1/files/embed \
  -H "Authorization: Bearer <YOUR_RSK_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
    "file_ids": ["file_abc123", "file_def456"]
  }'
```

The response returns per-file status:

```json theme={null}
{
  "results": [
    { "file_id": "file_abc123", "embedding_status": "queued", "chunk_count": null },
    { "file_id": "file_def456", "embedding_status": "queued", "chunk_count": null }
  ]
}
```

You can pass `"wait": true` to block until all embeddings finish (useful for small files in scripts).

***

## List your files

```bash theme={null}
curl "https://api.meshapi.ai/v1/files?limit=20&offset=0" \
  -H "Authorization: Bearer <YOUR_RSK_KEY>"
```

Response includes `files` (array of file status objects), `total`, `limit`, and `offset`.

***

## Common errors

| HTTP  | Meaning                                      |
| ----- | -------------------------------------------- |
| `401` | Missing or invalid API key                   |
| `402` | Balance or spend limit exceeded              |
| `422` | Invalid request body (check required fields) |
| `429` | Rate limit exceeded — back off and retry     |

***

## File management

| Action                   | Endpoint                  |
| ------------------------ | ------------------------- |
| Upload (init signed URL) | `POST /v1/files`          |
| List                     | `GET /v1/files`           |
| Get file status          | `GET /v1/files/{file_id}` |
| Trigger embedding        | `POST /v1/files/embed`    |
| Search                   | `POST /v1/files/search`   |

## Supported file types

| Type       | Extensions / MIMEs                                                                     |
| ---------- | -------------------------------------------------------------------------------------- |
| PDF        | `.pdf`                                                                                 |
| Plain text | `.txt`, `.md`, and all `text/*` variants                                               |
| Word       | `.docx`, `.doc`                                                                        |
| PowerPoint | `.pptx`                                                                                |
| Excel      | `.xlsx`, `.xls`                                                                        |
| CSV / TSV  | `.csv`, `.tsv`                                                                         |
| JSON       | `.json`                                                                                |
| XML        | `.xml`                                                                                 |
| HTML       | `.html`                                                                                |
| Code files | `.js`, `.ts`, `.py`, `.java`, `.c`, `.cpp`, `.cs`, `.go`, `.rb`, `.php`, `.sh`, `.sql` |
| Other      | `.parquet`, `.tex`                                                                     |
