Unrag
Examples

Admin Re-ingest Endpoint

An admin endpoint for re-indexing individual documents on demand.

When content is edited through a CMS, admin panel, or external system, you need a way to update the search index. This example shows how to build a secure admin endpoint that re-ingests specific documents.

The use case

Imagine you have a help center where support team members edit articles through an admin interface. When they save an article, the search index should update to reflect the changes. Rather than re-indexing everything, you target the specific document that changed.

Admin endpoint

This endpoint accepts a document identifier and content, then re-ingests it:

// app/api/admin/reingest/route.ts
import { createUnragEngine } from "@unrag/config";
import { verifyAdminToken } from "@/lib/auth";

export async function POST(request: Request) {
  // Verify the request is from an authenticated admin
  const authHeader = request.headers.get("authorization");
  const isAdmin = await verifyAdminToken(authHeader);
  
  if (!isAdmin) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }
  
  // Parse the request body
  const body = await request.json().catch(() => null);
  
  if (!body?.sourceId || typeof body.sourceId !== "string") {
    return Response.json(
      { error: "Missing or invalid sourceId" },
      { status: 400 }
    );
  }
  
  if (!body?.content || typeof body.content !== "string") {
    return Response.json(
      { error: "Missing or invalid content" },
      { status: 400 }
    );
  }
  
  try {
    const engine = createUnragEngine();
    
    const result = await engine.ingest({
      sourceId: body.sourceId,
      content: body.content,
      metadata: {
        ...body.metadata,
        reindexedAt: new Date().toISOString(),
        reindexedBy: body.adminId ?? "unknown",
      },
    });
    
    return Response.json({
      success: true,
      documentId: result.documentId,
      chunkCount: result.chunkCount,
      embeddingModel: result.embeddingModel,
      durationMs: result.durations.totalMs,
    });
  } catch (error) {
    console.error("Reingest failed:", error);
    return Response.json(
      { error: "Ingestion failed. Check server logs." },
      { status: 500 }
    );
  }
}

Calling from your CMS

When content is saved in your admin interface, call the reingest endpoint:

// lib/cms/on-save.ts
async function onArticleSave(article: Article) {
  // Save to your database first
  await db.articles.update(article);
  
  // Then update the search index
  const response = await fetch("/api/admin/reingest", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Authorization": `Bearer ${getAdminToken()}`,
    },
    body: JSON.stringify({
      sourceId: `help:${article.id}`,
      content: `# ${article.title}\n\n${article.body}`,
      metadata: {
        articleId: article.id,
        title: article.title,
        category: article.category,
        lastUpdated: article.updatedAt,
      },
      adminId: getCurrentAdminId(),
    }),
  });
  
  if (!response.ok) {
    console.error("Failed to update search index:", await response.text());
    // Decide how to handle: retry, queue, alert, etc.
  }
}

Webhook integration

If your CMS supports webhooks, set up automatic reindexing:

// app/api/webhooks/cms/route.ts
import { createUnragEngine } from "@unrag/config";
import { verifyWebhookSignature } from "@/lib/webhooks";

export async function POST(request: Request) {
  // Verify the webhook is legitimate
  const signature = request.headers.get("x-cms-signature");
  const body = await request.text();
  
  if (!verifyWebhookSignature(signature, body)) {
    return Response.json({ error: "Invalid signature" }, { status: 401 });
  }
  
  const payload = JSON.parse(body);
  
  // Handle different event types
  if (payload.event === "article.updated" || payload.event === "article.created") {
    const engine = createUnragEngine();
    
    await engine.ingest({
      sourceId: `help:${payload.article.id}`,
      content: payload.article.content,
      metadata: {
        title: payload.article.title,
        category: payload.article.category,
      },
    });
    
    return Response.json({ success: true });
  }
  
  if (payload.event === "article.deleted") {
    // You might want to add a delete capability to your store adapter
    // For now, the orphaned embeddings don't affect search accuracy
    return Response.json({ success: true, action: "ignored" });
  }
  
  return Response.json({ success: true, action: "unhandled" });
}

Batch reindexing

Sometimes you need to reindex multiple documents at once:

// app/api/admin/reingest-batch/route.ts
export async function POST(request: Request) {
  const { documents } = await request.json();
  
  if (!Array.isArray(documents) || documents.length === 0) {
    return Response.json({ error: "No documents provided" }, { status: 400 });
  }
  
  const engine = createUnragEngine();
  const results = [];
  
  for (const doc of documents) {
    try {
      const result = await engine.ingest({
        sourceId: doc.sourceId,
        content: doc.content,
        metadata: doc.metadata,
      });
      results.push({ sourceId: doc.sourceId, success: true, chunkCount: result.chunkCount });
    } catch (error) {
      results.push({ sourceId: doc.sourceId, success: false, error: error.message });
    }
  }
  
  return Response.json({
    total: documents.length,
    successful: results.filter((r) => r.success).length,
    failed: results.filter((r) => !r.success).length,
    results,
  });
}

This makes it easy to build admin UIs that let you select multiple articles and reindex them all at once.

On this page

RAG handbook banner image

Free comprehensive guide

Complete RAG Handbook

Learn RAG from first principles to production operations. Tackle decisions, tradeoffs and failure modes in production RAG operations

The RAG handbook covers retrieval augmented generation from foundational principles through production deployment, including quality-latency-cost tradeoffs and operational considerations. Click to access the complete handbook.