> ## 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.

# RAG (Files & Search)

> Upload documents, generate embeddings, and search with vector similarity.

MeshAPI RAG lets you upload documents, embed them as vectors, and run semantic search queries against them.

<Warning>
  The RAG API has no DELETE endpoint — uploaded files are permanent and cannot be removed programmatically.
</Warning>

## Full workflow

The upload → embed → search lifecycle is 6 steps:

<Steps>
  <Step title="Init upload">
    Request a signed upload URL and get a `file_id`.
  </Step>

  <Step title="PUT to signed URL">
    Upload the file bytes directly to the signed URL using a plain HTTP PUT.
  </Step>

  <Step title="Poll upload status">
    Call `rag.get(file_id)` until `upload_status == "ready"`.
  </Step>

  <Step title="Embed">
    Call `rag.embed([file_id])` to start the embedding process.
  </Step>

  <Step title="Poll embedding status">
    Call `rag.get(file_id)` until `embedding_status == "ready"`.
  </Step>

  <Step title="Search">
    Run a semantic search query against the embedded file.
  </Step>
</Steps>

## Code examples

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import time
    import httpx
    from meshapi import MeshAPI
    from meshapi import InitUploadRequest, BulkEmbedRequest, SearchRequest

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

    content = b"The quick brown fox jumps over the lazy dog."
    mime_type = "text/plain"

    # Step 1: Init upload
    upload = client.rag.init_upload(
        InitUploadRequest(file_name="my-doc.txt", mime_type=mime_type, embed=False)
    )
    file_id = upload.file_id

    # Step 2: PUT to signed URL
    httpx.put(upload.signed_url, content=content, headers={"Content-Type": mime_type}).raise_for_status()

    # Step 3: Poll upload status
    deadline = time.monotonic() + 30
    while time.monotonic() < deadline:
        status = client.rag.get(file_id)
        if status.upload_status == "ready":
            break
        time.sleep(2)

    # Step 4: Embed
    client.rag.embed(BulkEmbedRequest(file_ids=[file_id]))

    # Step 5: Poll embedding status
    deadline = time.monotonic() + 90
    while time.monotonic() < deadline:
        status = client.rag.get(file_id)
        if status.embedding_status == "ready":
            break
        elif status.embedding_status == "failed":
            raise RuntimeError(f"embedding failed: {status.last_error_code}")
        time.sleep(3)

    # Step 6: Search
    results = client.rag.search(
        SearchRequest(query="fox jumps", top_k=5, file_ids=[file_id])
    )
    for r in results.results:
        print(f"score={r.score:.4f} text={r.text[:80]}")
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    const content = "The quick brown fox jumps over the lazy dog.";
    const mimeType = "text/plain";

    // Step 1: Init upload
    const upload = await client.rag.initUpload({
      file_name: "my-doc.txt",
      mime_type: mimeType,
      embed: false,
    });
    const fileId = upload.file_id;

    // Step 2: PUT to signed URL
    await fetch(upload.signed_url, {
      method: "PUT",
      body: content,
      headers: { "Content-Type": mimeType },
    });

    // Step 3: Poll upload status
    const uploadDeadline = Date.now() + 30_000;
    while (Date.now() < uploadDeadline) {
      const s = await client.rag.get(fileId);
      if (s.upload_status === "ready") break;
      await new Promise(r => setTimeout(r, 2_000));
    }

    // Step 4: Embed
    await client.rag.embed({ file_ids: [fileId] });

    // Step 5: Poll embedding status
    const embedDeadline = Date.now() + 90_000;
    while (Date.now() < embedDeadline) {
      const s = await client.rag.get(fileId);
      if (s.embedding_status === "ready") break;
      if (s.embedding_status === "failed") throw new Error(`embedding failed: ${s.last_error_code}`);
      await new Promise(r => setTimeout(r, 3_000));
    }

    // Step 6: Search
    const results = await client.rag.search({
      query: "fox jumps",
      top_k: 5,
      file_ids: [fileId],
    });
    results.results.forEach(r => console.log(`score=${r.score.toFixed(4)} text=${r.text.slice(0, 80)}`));
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    content := []byte("The quick brown fox jumps over the lazy dog.")
    mimeType := "text/plain"

    // Step 1: Init upload
    embedFalse := false
    upload, err := client.RAG.InitUpload(ctx, meshapi.InitUploadRequest{
        FileName: "my-doc.txt",
        MimeType: mimeType,
        Embed:    &embedFalse,
    })
    if err != nil {
        log.Fatal(err)
    }
    fileID := upload.FileID

    // Step 2: PUT to signed URL
    req, _ := http.NewRequest(http.MethodPut, upload.SignedURL, bytes.NewReader(content))
    req.Header.Set("Content-Type", mimeType)
    http.DefaultClient.Do(req)

    // Step 3: Poll upload status
    deadline := time.Now().Add(30 * time.Second)
    for time.Now().Before(deadline) {
        s, _ := client.RAG.Get(ctx, fileID)
        if s.UploadStatus == "ready" {
            break
        }
        time.Sleep(2 * time.Second)
    }

    // Step 4: Embed
    client.RAG.Embed(ctx, meshapi.BulkEmbedRequest{FileIDs: []string{fileID}})

    // Step 5: Poll embedding status
    deadline = time.Now().Add(90 * time.Second)
    for time.Now().Before(deadline) {
        s, _ := client.RAG.Get(ctx, fileID)
        if s.EmbeddingStatus == "ready" {
            break
        }
        time.Sleep(3 * time.Second)
    }

    // Step 6: Search
    topK := 5
    results, err := client.RAG.Search(ctx, meshapi.SearchRequest{
        Query:   "fox jumps",
        TopK:    &topK,
        FileIDs: []string{fileID},
    })
    for _, r := range results.Results {
        fmt.Printf("score=%.4f\n", r.Score)
    }
    ```
  </Tab>
</Tabs>

## List files

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    page = client.rag.list(limit=50, offset=0)
    print(f"total={page.total}")
    for f in page.files:
        print(f.file_id, f.embedding_status)
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    const page = await client.rag.list({ limit: 50, offset: 0 });
    console.log(`total=${page.total}`);
    page.files.forEach(f => console.log(f.file_id, f.embedding_status));
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    limit := 50
    offset := 0
    page, err := client.RAG.List(ctx, meshapi.ListRagFilesParams{
        Limit:  &limit,
        Offset: &offset,
    })
    for _, f := range page.Files {
        fmt.Println(f.FileID, f.EmbeddingStatus)
    }
    ```
  </Tab>
</Tabs>

## File statuses

| Field              | Values                                  |
| ------------------ | --------------------------------------- |
| `upload_status`    | `pending`, `ready`, `failed`            |
| `embedding_status` | `pending`, `running`, `ready`, `failed` |

## Search options

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

## RAG chat

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

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    from meshapi import ChatCompletionParams, ChatMessage, SearchRequest

    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)
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    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);
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    results, err := client.RAG.Search(ctx, meshapi.SearchRequest{
        Query: "What is the refund policy?",
        TopK:  meshapi.Int(3),
    })
    if err != nil {
        log.Fatal(err)
    }

    var builder strings.Builder
    for _, r := range results.Results {
        builder.WriteString(r.Text + "\n\n")
    }

    reply, err := client.Chat.Completions.Create(ctx, meshapi.ChatCompletionParams{
        Model: meshapi.String("openai/gpt-4o-mini"),
        Messages: []meshapi.ChatMessage{
            {Role: "system", Content: "Answer using only the context below.\n\n" + builder.String()},
            {Role: "user", Content: "What is the refund policy?"},
        },
    })
    ```
  </Tab>
</Tabs>
