Examples
Example: Knowledge Base Manager (C#)
Build a knowledge base REST API in C# using ASP.NET Core Minimal API and the Aether .NET SDK.
This example creates a complete knowledge base manager with document CRUD, semantic search, and a RAG-powered chat endpoint that uses Anthropic's Claude to answer questions grounded in your documents. The entire application lives in a single Program.cs file using modern C# top-level statements.
Quick start
If you already have .NET 8+ and AETHER_API_KEY set:
- Clone the project and install dependencies:
dotnet new web -n KnowledgeBaseApi
cd KnowledgeBaseApi
dotnet add package AetherDb.Sdk
Replace
Program.cswith the full source code below.Run it:
dotnet run
The API will be available at http://localhost:5000.
Project setup
Create a new ASP.NET Core empty project and add the Aether SDK:
dotnet new web -n KnowledgeBaseApi
cd KnowledgeBaseApi
dotnet add package AetherDb.Sdk
The NuGet package is AetherDb.Sdk; the namespace used in code is Aether.Sdk.
Open Program.cs and replace its contents. The sections below walk through each part of the file.
Application bootstrap
Start with the builder setup, the Aether client, and an HttpClient for calling the Anthropic API in the chat endpoint.
using System.Text;
using System.Text.Json;
using System.Net.Http.Json;
using Aether.Sdk;
var builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseUrls("http://localhost:5000");
var app = builder.Build();
var aether = new AetherClient(new AetherClientOptions { ApiKey = Environment.GetEnvironmentVariable("AETHER_API_KEY") });
var httpClient = new HttpClient();
Document endpoints
These endpoints handle listing, inserting, retrieving, and soft-deleting documents. The GET /api/documents/{id} endpoint fetches both metadata and the full document content by combining GetAsync and DownloadAsync.
app.MapGet("/api/documents", async (int? offset, int? limit) =>
{
var result = await aether.ListAsync(offset ?? 0, limit ?? 20);
return Results.Ok(new { result.Documents, result.Total, result.HasMore });
});
app.MapPost("/api/documents", async (HttpContext ctx) =>
{
var body = await ctx.Request.ReadFromJsonAsync<InsertRequest>();
if (body is null || string.IsNullOrWhiteSpace(body.Text))
return Results.BadRequest(new { error = "Missing 'text' field" });
var doc = await aether.InsertTextAsync(body.Text, body.Title ?? "untitled.txt");
return Results.Created($"/api/documents/{doc.Id}", doc);
});
app.MapGet("/api/documents/{id}", async (string id) =>
{
var doc = await aether.GetAsync(id);
var bytes = await aether.DownloadAsync(id);
var content = Encoding.UTF8.GetString(bytes);
return Results.Ok(new { doc, content });
});
app.MapDelete("/api/documents/{id}", async (string id) =>
{
await aether.DeleteAsync(id);
return Results.Ok(new { message = "Document soft-deleted", docId = id });
});
app.MapPost("/api/documents/{id}/restore", async (string id) =>
{
await aether.RestoreAsync(id);
return Results.Ok(new { message = "Document restored", docId = id });
});
The InsertRequest record is defined at the bottom of the file alongside other helper types.
Search and chat
The search endpoint wraps SearchAsync with query-string parameters. The chat endpoint implements a RAG pipeline: it calls RetrieveAsync to get relevant passages with content in a single round-trip, builds a context string, and sends everything to the Anthropic API.
app.MapGet("/api/search", async (string q, int? k) =>
{
var results = await aether.SearchAsync(q, k ?? 5);
return Results.Ok(results.Select(r => new
{
r.DocId,
r.Score,
r.Title
}));
});
app.MapPost("/api/chat", async (HttpContext ctx) =>
{
var body = await ctx.Request.ReadFromJsonAsync<ChatRequest>();
if (body is null || string.IsNullOrWhiteSpace(body.Message))
return Results.BadRequest(new { error = "Missing 'message' field" });
var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY");
if (string.IsNullOrEmpty(apiKey))
return Results.Json(
new { error = "ANTHROPIC_API_KEY environment variable is not set" },
statusCode: 503);
// Step 1: Retrieve relevant passages (search + content in one call)
var searchResults = await aether.RetrieveAsync(body.Message, body.K ?? 3);
var context = string.Join("\n\n",
searchResults.Select(r => $"[Source: {r.Title}]\n{r.Content}"));
// Step 2: Call Anthropic API via HttpClient
var request = new HttpRequestMessage(HttpMethod.Post,
"https://api.anthropic.com/v1/messages");
request.Headers.Add("x-api-key", apiKey);
request.Headers.Add("anthropic-version", "2023-06-01");
request.Content = JsonContent.Create(new
{
model = "claude-sonnet-4-20250514",
max_tokens = 1024,
system = $"Answer the user's question using only the following context. "
+ $"Cite which source you used.\n\n{context}",
messages = new[]
{
new { role = "user", content = body.Message }
}
});
var response = await httpClient.SendAsync(request);
var json = await response.Content.ReadFromJsonAsync<JsonElement>();
var answer = json.GetProperty("content")[0].GetProperty("text").GetString();
return Results.Ok(new
{
answer,
sources = searchResults.Select(r => new { r.DocId, r.Title, r.Score })
});
});
Stats endpoint
Expose the total document count on a single endpoint for monitoring. ListAsync returns pagination metadata with every page, so requesting a single document is enough to read the total.
app.MapGet("/api/stats", async () =>
{
var page = await aether.ListAsync(0, 1);
return Results.Ok(new { totalDocuments = page.Total });
});
Request models and startup
Define the helper records and start the application.
app.Run();
record InsertRequest(string Text, string? Title);
record ChatRequest(string Message, int? K);
That is the complete Program.cs. The full file is around 130 lines of application code with no controllers, no startup class, and no dependency injection configuration beyond what ASP.NET Core provides by default.
Testing the API
Once the app is running, try inserting a document and searching for it:
# Insert a document
curl -X POST http://localhost:5000/api/documents \
-H "Content-Type: application/json" \
-d '{"text": "Employees accrue 20 days of PTO per year.", "title": "pto-policy.txt"}'
# List all documents
curl http://localhost:5000/api/documents
# Search
curl "http://localhost:5000/api/search?q=vacation%20days&k=3"
# Chat (requires ANTHROPIC_API_KEY)
curl -X POST http://localhost:5000/api/chat \
-H "Content-Type: application/json" \
-d '{"message": "How many vacation days do I get?"}'
Next steps
- .NET quickstart -- install the .NET SDK
- Authentication -- authenticate with your API key
- API Reference -- full endpoint documentation