C# / .NET (Modern)
Production-grade C# SDK for the Linkit API — .NET 10, System.Text.Json source generation, IAsyncEnumerable, zero external dependencies.
C# / .NET SDK (Modern)
Package
sa.linkit.LinkitDotNet · .NET 10+ · System.Text.Json · Zero external dependencies
Also Available: .NET Framework 4.8 (Legacy)
Need to target legacy .NET Framework? See the C# / .NET Legacy SDK — same API surface, backed by Newtonsoft.Json and Task-based async patterns.
Modern vs Legacy at a Glance
| Feature | LinkitDotNet (Modern) | LinkitDotNetLegacy |
|---|---|---|
| Target | .NET 10+ | .NET Framework 4.8 |
| JSON | System.Text.Json source gen | Newtonsoft.Json cached settings |
| Streaming | IAsyncEnumerable | Task-based batch processing |
| External Deps | Zero | Newtonsoft.Json, polyfills |
| NuGet Package | LinkitDotNet | LinkitDotNetLegacy |
ConfigureAwait | Not needed | Required (ConfigureAwait(false)) |
| Allocations | 3 per create op | 5 per create op |
| Namespace | LinkitDotNet.* | LinkitDotNetLegacy.* |
Installation
nuget install LinkitDotNetdotnet add package LinkitDotNet<PackageReference Include="LinkitDotNet" Version="0.0.4" />Quick Start
Initialize the Client
using LinkitDotNet.Client;
using LinkitDotNet.Configuration;
await using var client = await LinkitClient.CreateClientAsync(
config => config
.WithBaseUrl("https://linkit.works/api/v1")
.WithTimeout(TimeSpan.FromSeconds(30))
.WithRetryPolicy(retry => retry.MaxRetries = 3),
jwtToken: "your-jwt-token"
);Create a Product
var product = await client.Products()
.Create()
.WithId("PROD-001")
.WithName("Premium Widget", "ويدجت متميز")
.WithPrice(99.99, vatPercentage: 0.15)
.WithBarcode("1234567890123")
.EnableQuickCommerce()
.AsEnabled()
.ExecuteAsync();Manage SKU Inventory
// SKUs are auto-generated — never create manually, only update
await client.Skus()
.UpdateStock("AUTO-SKU-ID", "BR-RIYADH-001")
.SetQuantity(500)
.SetAvailability(true)
.ExecuteAsync();Stream Large Datasets (Modern-Only Feature)
// IAsyncEnumerable — memory-efficient pagination
await client.Products()
.Stream()
.Where(p => p.AveragePrice > 1000)
.WithConcurrency(10)
.Process(async product => {
await ProcessProductAsync(product);
})
.ProcessAllAsync();Legacy Alternative
The Legacy SDK uses ProcessInBatches() with Task-based patterns instead of IAsyncEnumerable.
Configuration
await using var client = await LinkitClient.CreateClientAsync(
config => config
.WithBaseUrl("https://linkit.works/api/v1")
.WithTimeout(TimeSpan.FromMinutes(2))
.WithMaxConcurrentRequests(100)
.WithRetryPolicy(retry => {
retry.MaxRetries = 5;
retry.InitialDelay = TimeSpan.FromMilliseconds(200);
retry.BackoffMultiplier = 2.0;
retry.MaxDelay = TimeSpan.FromSeconds(30);
})
.WithDefaultHeader("X-Client-Version", "1.0.0")
.WithDefaultHeader("X-Correlation-Id", Guid.NewGuid().ToString())
.WithRequestLogging(
onBeforeRequest: req => logger.LogTrace("[Outbound] {Method} {Uri}", req.Method, req.RequestUri),
onAfterResponse: res => logger.LogTrace("[Inbound] {StatusCode}", res.StatusCode))
.OnError(error => logger.LogError(error, "Client error occurred"))
.OnInitialized(() => logger.LogInformation("Linkit client initialized successfully")),
jwtToken,
errorHandler => errorHandler
.WithCircuitBreaker(failureThreshold: 10, resetTimeout: TimeSpan.FromMinutes(2))
.WithRetryPolicy(maxRetries: 3, retryDelay: TimeSpan.FromSeconds(1)),
logger
);Services Reference
| Service | Methods | Description |
|---|---|---|
client.Products() | Create, Query, Update, Delete, Stream | Central product catalog |
client.Skus() | Query, Update, UpdateStock | Stock Keeping Units per branch (update-only) |
client.Branches() | Create, Query, Update, Delete, FindNearby | Physical locations |
client.Customers() | Create, Query, Search, Lookup, Stream, Groups | Customer profiles |
client.Orders() | Query, UpdateStatus | Order management |
client.Offers() | Create, Query, Update, UpdateStatus, Delete, BulkUpsert | Promotions & discounts |
client.Brands() | Create, Query, Update, Delete | Brand management |
client.Categories() | Create, Query, Update, Delete | Product categories |
client.Generics() | Create, Query, Update, Delete | Generic entities |
client.Health() | CheckAsync, PingAsync | System health monitoring |
client.Integrations() | GetByIdAsync, ExecuteAsync, EnableAsync, DisableAsync | Platform connections |
Products
Create
var product = await client.Products()
.Create()
.WithId("PROD-001")
.WithName("Premium Widget", "ويدجت متميز")
.WithPrice(99.99, vatPercentage: 0.15)
.WithBarcode("1234567890123")
.WithAttributes(attr => attr
.IsFastMoving()
.Buffer(25)
.SitemapPriority(0.9))
.EnableQuickCommerce()
.AsEnabled()
.ExecuteAsync();Query with Filtering
var products = await client.Products()
.Query()
.SearchFor("laptop")
.EnabledOnly()
.FastMovingOnly()
.QuickCommerceOnly()
.Page(1)
.Take(50)
.OrderBy("-created")
.WithMetadata("query_type", "inventory_check")
.ExecuteAsync();Stream (IAsyncEnumerable)
await client.Products()
.Stream()
.Where(p => p.AveragePrice > 1000)
.WithConcurrency(10)
.Process(async product => {
await ProcessProductAsync(product);
})
.ProcessAllAsync();SKU Operations (Update Only)
Important
SKUs are auto-generated when you create products and branches. Never create SKUs manually — only update existing ones.
// Update stock levels
await client.Skus()
.UpdateStock("EXISTING-SKU-ID", "BRANCH-ID")
.SetQuantity(150)
.SetAvailability(true)
.WithMetadata("reason", "restocking")
.ExecuteAsync();
// Update SKU with marketplace IDs
await client.Skus()
.Update("EXISTING-SKU-ID", "BRANCH-ID")
.WithPrice(899.99)
.WithStock(quantity: 200, maxQuantity: 1000)
.WithReorderThreshold(50)
.WithMarketplaceIds(ids => ids
.Amazon("AMZ-SKU-123", "B08N5WRWNW")
.Noon("N123456789", "Z87654321")
.Salla("SALLA-987")
.Zid("ZID-654"))
.ExecuteAsync();Branches
// Create
var branch = await client.Branches()
.Create()
.WithId("STORE-001")
.WithName("Downtown Store", "متجر وسط المدينة")
.AtLocation(25.2048, 55.2708)
.WithStatus(BranchStatus.Published)
.AsActive()
.WithWorkingHours(hours => hours
.Monday("09:00", "21:00")
.Friday("14:00", "22:00")
.Saturday("10:00", "20:00")
.ClosedOn(DayOfWeek.Sunday))
.ExecuteAsync();
// Find nearby
var nearbyBranches = await client.Branches()
.FindNearby(lat: 25.2048, lon: 55.2708)
.WithinRadius(10)
.ActiveOnly()
.WithStatus(BranchStatus.Published)
.ExecuteAsync();Customers
// Create
var customer = await client.Customers()
.Create()
.WithName("Ahmad", "Hamdi")
.WithEmail("ahmad@example.com")
.WithPhone("+966500000000")
.AsType("vip")
.WithStatus("active")
.ExecuteAsync();
// Lookup
var found = await client.Customers()
.Lookup()
.ByEmail("ahmad@example.com")
.ExecuteAsync();
// Address management
var address = await client.Customers()
.ForCustomer(customer.Id)
.CreateAddress()
.WithLabel("Home")
.WithAddress("123 Main Street", "Apt 4B")
.InLocation("Riyadh", "Riyadh")
.InCountry("SA")
.AsDefault()
.ExecuteAsync();Batch Operations
var batchResult = await client.Batch()
.WithContinueOnError(true)
.WithTimeout(TimeSpan.FromMinutes(5))
.CreateProduct(p => p
.WithId("BATCH-001")
.WithName("Batch Product 1", "منتج دفعة 1")
.WithPrice(99.99))
.CreateBranch(b => b
.WithId("BATCH-STORE")
.WithName("Batch Store", "متجر دفعة")
.AtLocation(25.2, 55.3))
.ExecuteAsync();
Console.WriteLine($"Success: {batchResult.SuccessfulOperations}/{batchResult.TotalOperations}");Error Handling
using LinkitDotNet.Exceptions;
try
{
var sku = await client.Skus().Query().ForProduct("nonexistent").ExecuteAsync();
}
catch (LinkitNotFoundException ex)
{
Console.WriteLine($"Not found: {ex.ResourceId}");
}
catch (LinkitRateLimitException ex)
{
Console.WriteLine($"Rate limited — retry after {ex.RetryAfterMs}ms");
}
catch (LinkitValidationException ex)
{
foreach (var error in ex.Details)
Console.WriteLine($" {error.Key}: {error.Value}");
}
catch (LinkitApiException ex)
{
Console.WriteLine($"API error {ex.StatusCode}: {ex.Message}");
}Exception Hierarchy
| Exception | Status | Description |
|---|---|---|
LinkitException | — | Base for all SDK errors |
LinkitInitializationException | — | Invalid configuration |
LinkitApiException | Any | API error responses |
LinkitAuthenticationException | 401 | Invalid credentials |
LinkitAuthorizationException | 403 | Insufficient permissions |
LinkitNotFoundException | 404 | Resource not found |
LinkitValidationException | 400 | Request validation failed |
LinkitConflictException | 409 | State conflict |
LinkitRateLimitException | 429 | Rate limit exceeded |
LinkitServerException | 5xx | Server-side error |
LinkitSerializationException | — | JSON parse failure |
Resilience
Zero Configuration Required
The resilience pipeline is active by default. Transient errors (5xx, 429, timeouts) are automatically retried with exponential backoff + jitter. The circuit breaker trips after 5 consecutive failures and recovers after 60 seconds.
Override defaults via configuration:
config => config
.WithRetryPolicy(retry => {
retry.MaxRetries = 5;
retry.InitialDelay = TimeSpan.FromMilliseconds(200);
retry.BackoffMultiplier = 2.0;
})
.WithMaxConcurrentRequests(20)Performance
| Operation | Allocations | Mean Time | Memory |
|---|---|---|---|
| Create Product | 3 | 42.3 ms | 4.2 KB |
| List Products (100) | 5 | 78.6 ms | 11.8 KB |
| Update SKU Stock | 2 | 21.7 ms | 1.9 KB |
| Stream Products (1000) | 12 | 398.4 ms | 17.2 KB |
| Batch Operation (10) | 8 | 156.2 ms | 8.6 KB |
Benchmarks run on .NET 10.0, Intel i7-12700K, 32GB RAM
Why Modern is Faster
- Source-generated JSON: Zero-reflection
System.Text.Jsonserialization eliminates startup overhead - IAsyncEnumerable: Streams pages on-demand rather than buffering entire datasets
- No external deps: Avoids Newtonsoft.Json's heavier allocation patterns
await using: Native async disposal withoutConfigureAwaitceremony
SDK Reference
Official Linkit SDKs — integrate the Linkit API from any language with production-grade resilience, fluent builders, and type safety.
C# / .NET Framework 4.8 (Legacy)
Enterprise-grade C# SDK for the Linkit API — .NET Framework 4.8, Newtonsoft.Json, task-based async, fluent builders, NuGet package.