C# / .NET
The official tachyon-sdk client for .NET.
dotnet add package TachyonSdkTargets .NET 8 (LTS). Zero third-party dependencies — just
System.Net.Http and System.Text.Json, both in the BCL.
Quickstart
using Tachyon.Sdk;
var client = new TachyonClient(new TachyonClientOptions
{
Url = "http://localhost:8108",
ApiKey = "my-admin-key",
});
await client.Collections.CreateAsync(new CollectionSchema
{
Name = "products",
Fields =
[
new FieldSchema { Name = "title", Type = FieldType.Text },
new FieldSchema { Name = "brand", Type = FieldType.Keyword, Facet = true },
new FieldSchema { Name = "price", Type = FieldType.Int, Filter = true, Sort = true },
],
});
var collection = client.Collection("products");
await collection.Documents.IndexAsync(
new Document { ["id"] = "1", ["title"] = "Wireless Mouse", ["brand"] = "Logitech", ["price"] = 2999 },
new Document { ["id"] = "2", ["title"] = "Mechanical Keyboard", ["brand"] = "Razer", ["price"] = 8999 });
var results = await collection.SearchAsync(new SearchParams { Q = "wireless mouse" });
foreach (var hit in results.Hits)
{
Console.WriteLine($"{hit.Document["title"]} {hit.TextMatch}");
}Document is a type alias for System.Text.Json.Nodes.JsonObject declared
via a global using inside the package — aliases don't cross assembly
boundaries in C#, so add the same line to your own project if you want to
write Document instead of JsonObject:
global using Document = System.Text.Json.Nodes.JsonObject;Or just use JsonObject directly; it's the exact same type either way.
Client options
var client = new TachyonClient(new TachyonClientOptions
{
Url = "http://localhost:8108",
ApiKey = "...", // admin key (read/write) or search key (read-only)
Timeout = TimeSpan.FromSeconds(15), // default 15s; ignored if HttpClient is supplied
Headers = new Dictionary<string, string> { ["X-Custom"] = "value" },
HttpClient = myHttpClient, // override transport (testing, custom pooling)
});Every method takes an optional trailing CancellationToken, in keeping with
.NET convention.
Collections
client.Collections.CreateAsync(schema); // POST /collections
client.Collections.ListAsync(); // GET /collections
client.Collections.RetrieveAsync(name); // GET /collections/{name}
client.Collections.DeleteAsync(name); // DELETE /collections/{name}FieldSchema's optional attributes (Facet, Filter, Sort, Index,
Optional, Boost) are nullable, so leaving them unset doesn't override
the server's own default.
Documents
var collection = client.Collection("products");
collection.Documents.IndexAsync(doc1, doc2, doc3); // POST /collections/{name}/documents (upsert by id, params array)
collection.Documents.RetrieveAsync(id); // GET /collections/{name}/documents/{id}
collection.Documents.DeleteAsync(id); // DELETE /collections/{name}/documents/{id}IndexAsync always resolves at the HTTP level even if individual documents
are rejected — check NumFailed and Results on the response.
Search
await collection.SearchAsync(new SearchParams
{
Q = "wireless mouse",
QueryBy = ["title", "description"],
Filter = "brand:=Logitech && price:<5000",
Sort = "_text_match:desc,price:asc",
Facet = ["brand", "year"],
Limit = 20,
Offset = 0,
Prefix = true,
TypoTolerance = true,
MatchMode = MatchMode.All, // or MatchMode.Any
});FoundIsExact on the response is false once block-max WAND pruning has
skipped part of a term's postings for a broad query — at that point Found
and facet counts are a lower bound, not an exact count.
Autocomplete
await collection.SuggestAsync(new SuggestParams { Q = "wir", Limit = 5 });Analytics
await client.Analytics.TopAsync(new AnalyticsQueryParams { Collection = "products", Limit = 10 });
await client.Analytics.ZeroResultsAsync(new AnalyticsQueryParams { Collection = "products" });
await client.Analytics.LatencyAsync();Analytics are in-memory only and reset when the server restarts.
Operations
await client.HealthAsync(); // GET /health — no API key required
await client.MetricsAsync(); // GET /metrics — Prometheus exposition format, returned as textErrors
Every non-2xx response throws a TachyonException subclass carrying the
server's stable Code and the HTTP StatusCode:
try
{
await client.Collections.RetrieveAsync("does-not-exist");
}
catch (TachyonNotFoundException e)
{
Console.WriteLine($"{e.Code} {e.StatusCode} {e.Message}"); // collection_not_found 404 ...
}
catch (TachyonException e)
{
// any other API error
}| Exception | Status | Codes |
|---|---|---|
TachyonRequestException | 400 | invalid_schema, invalid_document, invalid_query, invalid_json |
TachyonAuthenticationException | 401 | unauthorized |
TachyonAuthorizationException | 403 | forbidden |
TachyonNotFoundException | 404 | collection_not_found, document_not_found |
TachyonConflictException | 409 | collection_exists |
TachyonServerException | 5xx | corrupt_data, io_error, internal_error |
Network failures and timeouts throw TachyonConnectionException /
TachyonTimeoutException instead, since there's no server response to read
a code from.
Source
csharp/ in the
tachyon-sdk repo, including the mocked unit suite (Tachyon.Sdk.Tests) and
a separate integration test project that runs against a real server.