Klau.Sdk 0.8.0

dotnet add package Klau.Sdk --version 0.8.0
                    
NuGet\Install-Package Klau.Sdk -Version 0.8.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Klau.Sdk" Version="0.8.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Klau.Sdk" Version="0.8.0" />
                    
Directory.Packages.props
<PackageReference Include="Klau.Sdk" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Klau.Sdk --version 0.8.0
                    
#r "nuget: Klau.Sdk, 0.8.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Klau.Sdk@0.8.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Klau.Sdk&version=0.8.0
                    
Install as a Cake Addin
#tool nuget:?package=Klau.Sdk&version=0.8.0
                    
Install as a Cake Tool

Klau .NET SDK

Official .NET SDK for the Klau API. Built for dev teams integrating roll-off waste hauling operations into .NET applications.

dotnet add package Klau.Sdk

Requires .NET 8.0+. Dependencies: Microsoft.Extensions.Logging.Abstractions, Microsoft.Extensions.DependencyInjection.Abstractions, Microsoft.Extensions.Diagnostics.HealthChecks.

Quick Start

Generate an API key in Settings > Developer in your Klau dashboard. API keys start with kl_live_, are scoped to specific permissions, and can be revoked without affecting user credentials.

using Klau.Sdk;

using var klau = new KlauClient("kl_live_your_api_key_here");

// Verify your connection — works on every account, even empty ones
var company = await klau.Company.GetAsync();
Console.WriteLine($"Connected to {company.Name}");

// Check dispatch readiness (drivers, trucks, yards configured?)
var readiness = await klau.Readiness.CheckAsync();
Console.WriteLine($"Can go live: {readiness.CanGoLive}");

Integration Guide: Jobs In, Assignments Out

Most integrations follow the same pattern: push work orders from your backend into Klau, run dispatch optimization, then read back driver assignments. This section walks through the entire flow.

Step 1: Push jobs into Klau

You don't need to pre-create customers or sites. The Import API resolves customers and sites by name, auto-creates any that don't exist, and handles geocoding and drive-time caching in the background. Just push your work orders with names and addresses — Klau figures out the rest.

Use ExternalId on every job to correlate Klau records with your system's IDs — this is the key to reliable two-way sync.

Choosing the right method
Method Best for Limit Pre-created IDs needed?
SubmitAndWaitAsync Production syncs, large batches 1,000 jobs No — uses names/addresses
ImportAndWaitAsync Small batches, quick scripts ~100 jobs No — uses names/addresses
Jobs.CreateBatchAsync When you already have Klau IDs 100 jobs Yes — requires CustomerId + SiteId
Jobs.CreateAsync Single job creation 1 job Yes — requires CustomerId + SiteId

SubmitAndWaitAsync is the recommended path for production integrations. It accepts up to 1,000 jobs, returns immediately (no geocoding in the request path), processes everything in the background, and waits until the batch is fully processed and the drive-time cache is warm:

var batch = await klau.Import.SubmitAndWaitAsync(new ImportJobsRequest
{
    Jobs =
    [
        new ImportJobRecord
        {
            CustomerName = "Acme Construction",
            SiteName = "Main Office",
            SiteAddress = "456 Industrial Way",
            SiteCity = "San Luis Obispo",
            SiteState = "CA",
            SiteZip = "93401",
            JobType = "DELIVERY",
            ContainerSize = "20",
            TimeWindow = "MORNING",
            ExternalId = "WO-1001"
        },
        new ImportJobRecord
        {
            CustomerName = "Acme Construction",
            SiteName = "Warehouse",
            SiteAddress = "789 Commerce Dr",
            SiteCity = "San Luis Obispo",
            SiteState = "CA",
            SiteZip = "93401",
            JobType = "PICKUP",
            ContainerSize = "30",
            ExternalId = "WO-1002"
        }
    ]
});

Console.WriteLine($"Imported: {batch.Imported}, Skipped: {batch.Skipped}");
Console.WriteLine($"Created: {batch.CustomersCreated} customers, {batch.SitesCreated} sites");

// Drive-time cache is warm — safe to optimize now

For more control over polling, call SubmitJobsAsync and GetBatchStatusAsync separately:

var submit = await klau.Import.SubmitJobsAsync(request);
Console.WriteLine($"Batch {submit.BatchId}: {submit.JobCount} jobs queued");

AsyncImportBatchStatus status;
do
{
    await Task.Delay(2000);
    status = await klau.Import.GetBatchStatusAsync(submit.BatchId);
    Console.WriteLine($"Status: {status.Status} — {status.Processed}/{status.Total} processed, cache: {status.DriveTimeCacheStatus}");
}
while (!status.IsReadyForOptimization);
Sync import (small batches)

For smaller batches or quick scripts, ImportAndWaitAsync processes jobs synchronously and polls the drive-time cache:

var import = await klau.Import.ImportAndWaitAsync(new ImportJobsRequest
{
    Jobs =
    [
        new ImportJobRecord
        {
            CustomerName = "Acme Construction",
            SiteName = "Main Office",
            SiteAddress = "456 Industrial Way",
            SiteCity = "San Luis Obispo",
            SiteState = "CA",
            SiteZip = "93401",
            JobType = "DELIVERY",
            ContainerSize = "20",
            TimeWindow = "MORNING",
            ExternalId = "WO-1001"
        }
    ]
});

Console.WriteLine($"Imported: {import.Imported}, Created: {import.CustomersCreated} customers, {import.SitesCreated} sites");
Batch create (when you already have Klau IDs)

If your system already tracks Klau customer and site IDs (e.g. after an initial import), you can use CreateBatchAsync for faster job creation without the name-resolution overhead:

var result = await klau.Jobs.CreateBatchAsync(new List<CreateJobRequest>
{
    new()
    {
        CustomerId = "cust-1",
        SiteId = "site-1",
        Type = JobType.DELIVERY,
        ContainerSize = 20,
        RequestedDate = "2026-03-15",
        ExternalId = "WO-1001"
    }
});

foreach (var created in result.Created)
    Console.WriteLine($"Created {created.JobId} (external: {created.ExternalId})");

foreach (var error in result.Errors)
    Console.WriteLine($"Failed index {error.Index}: {error.Code} - {error.Message}");

Step 2: Optimize dispatch

Optimization is async. OptimizeAndWaitAsync handles polling for you.

var optimization = await klau.Dispatches.OptimizeAndWaitAsync(new OptimizeRequest
{
    Date = "2026-03-15",
    OptimizationMode = OptimizationMode.FULL_DAY
});

if (optimization.Status == OptimizationJobStatus.COMPLETED)
{
    var r = optimization.Result!;
    Console.WriteLine($"Plan grade: {r.PlanGrade} ({r.PlanQuality}/100)");
    Console.WriteLine($"Assigned: {r.AssignedJobs}/{r.TotalJobs}");
    Console.WriteLine($"Flow score: {r.FlowScore}/100");
    Console.WriteLine($"Drive times: {r.DriveTimeSource}"); // "API" (real) or "ESTIMATED" (haversine)
}

Or manage polling yourself for more control:

var job = await klau.Dispatches.StartOptimizationAsync(new OptimizeRequest
{
    Date = "2026-03-15"
});

while (job.Status is OptimizationJobStatus.PENDING or OptimizationJobStatus.RUNNING)
{
    await Task.Delay(2000);
    job = await klau.Dispatches.GetOptimizationStatusAsync(job.JobId);
}

Step 3: Read back assignments

After optimization, read the dispatch board to get driver routes with job sequences. Each job includes drive-time data showing how it will be reached:

var board = await klau.Dispatches.GetBoardAsync("2026-03-15");

foreach (var driver in board.Drivers)
{
    Console.WriteLine($"\n{driver.Name} ({driver.Id}): " +
        $"{driver.TotalDriveMinutes} min drive, {driver.TotalServiceMinutes} min service");

    foreach (var job in driver.Jobs.OrderBy(j => j.Sequence))
    {
        Console.WriteLine($"  #{job.Sequence} {job.Type} - {job.CustomerName} " +
            $"({job.ContainerSize}yd) [external: {job.ExternalId}]");

        // Drive-time fields (populated after optimization)
        Console.WriteLine($"    Drive: {job.DriveToMinutes:F1} min / {job.DriveToMiles:F1} mi " +
            $"(source: {job.DriveTimeSource})");
        // DriveTimeSource: "routing_engine" (real truck routing), "cached", "haversine" (estimate), or null
    }
}

// Unassigned jobs that couldn't be fit
foreach (var unassigned in board.UnassignedJobs)
    Console.WriteLine($"Unassigned: {unassigned.ExternalId} - {unassigned.CustomerName}");

Step 4: Publish to drivers

Once you're satisfied with the plan, publish it so drivers see their routes:

await klau.Dispatches.PublishAsync("2026-03-15");

Webhooks: Real-Time Event Delivery

Instead of polling for changes, register a webhook to receive events as they happen. Klau delivers events with HMAC-SHA256 signatures for verification.

Register a webhook

var webhook = await klau.Webhooks.CreateAsync(new CreateWebhookRequest
{
    Url = "https://your-app.com/api/klau-webhook",
    Events = ["job.assigned", "job.completed", "dispatch.optimized"],
    Description = "Production sync"
});

// Store this secret securely — it's only returned once
Console.WriteLine($"Webhook secret: {webhook.Secret}");

Receive and verify events

Use KlauWebhookValidator in your webhook endpoint to verify signatures and parse events:

// In your ASP.NET Core controller or minimal API
app.MapPost("/api/klau-webhook", async (HttpContext ctx) =>
{
    var validator = new KlauWebhookValidator("whsec_your_secret");

    var body = await new StreamReader(ctx.Request.Body).ReadToEndAsync();
    var signature = ctx.Request.Headers["Klau-Signature"].ToString();

    // Throws KlauWebhookException if signature is invalid or timestamp expired
    var evt = validator.ValidateAndParse(signature, body);

    switch (evt.Type)
    {
        case "job.assigned":
            var assigned = evt.Data.Deserialize<JobAssignedEvent>();
            // Sync assignment back to your system
            // assigned.JobId, assigned.DriverId, assigned.AssignmentSource
            break;

        case "job.completed":
            var completed = evt.Data.Deserialize<JobCompletedEvent>();
            // Close work order in your system
            break;

        case "dispatch.optimized":
            var optimized = evt.Data.Deserialize<DispatchOptimizedEvent>();
            // React to optimization: optimized.Metrics.AssignedJobs, etc.
            break;
    }

    return Results.Ok();
});

Or parse directly into a typed event if you know the type:

var evt = validator.ValidateAndParse<JobAssignedEvent>(signature, body);
Console.WriteLine($"Job {evt.Data.JobId} assigned to driver {evt.Data.DriverId}");

Available events

Event Fired when
job.created New job entered into Klau
job.assigned Job assigned to a driver (manual or optimization)
job.unassigned Job removed from a driver's route
job.status_changed Job transitions (IN_PROGRESS, COMPLETED, etc.)
job.completed Job finished (also fires status_changed)
dispatch.optimized Optimization run completed with metrics

Use "*" to subscribe to all events.

Manage webhooks

// List existing webhooks
var settings = await klau.Webhooks.GetSettingsAsync();
foreach (var endpoint in settings.WebhookEndpoints)
    Console.WriteLine($"{endpoint.Id}: {endpoint.Url} [{endpoint.Status}]");

// Disable/enable
await klau.Webhooks.SetEnabledAsync("webhook-id", false);

// Test connectivity
var test = await klau.Webhooks.TestAsync("webhook-id");
Console.WriteLine($"Test: {(test.Success ? "OK" : test.Error)} ({test.ResponseTime}ms)");

// Delete
await klau.Webhooks.DeleteAsync("webhook-id");

Enterprise: Multi-Division Operations

Parent company API keys can list all divisions and operate on any child tenant. Integrate once, manage everything through a single API key.

using var klau = new KlauClient("kl_live_corporate_api_key");

// List all divisions under your account
var divisions = await klau.Divisions.ListAsync();
foreach (var div in divisions)
    Console.WriteLine($"{div.Name}: {div.DriverCount} drivers, {div.JobCount} jobs");

// Operate on a specific division — thread-safe, no shared state
var region = klau.ForTenant(divisions[0].Id);
var board = await region.Dispatches.GetBoardAsync("2026-03-15");
var jobs = await region.Jobs.ListAsync(date: "2026-03-15");

// Or set/clear tenant context directly
klau.SetTenant(divisions[0].Id);
var customers = await klau.Customers.ListAsync();
klau.ClearTenant(); // revert to parent company context

// Aggregate usage across all divisions
var usage = await klau.Divisions.GetUsageSummaryAsync();
Console.WriteLine($"Total jobs: {usage.TotalJobs} across {usage.Divisions.Count} divisions");

Jobs

// List unassigned jobs for a date
var jobs = await klau.Jobs.ListAsync(date: "2026-03-15", status: JobStatus.UNASSIGNED);

foreach (var job in jobs.Items)
    Console.WriteLine($"{job.Type} - {job.CustomerName} - {job.ContainerSize}yd");

// Create a job
var newJob = await klau.Jobs.CreateAsync(new CreateJobRequest
{
    CustomerId = "customer-id",
    SiteId = "site-id",
    Type = JobType.DELIVERY,
    ContainerSize = 20,
    RequestedDate = "2026-03-15",
    TimeWindow = TimeWindow.MORNING
});

// Assign to a driver
await klau.Jobs.AssignAsync(newJob.Id, new AssignJobRequest
{
    DriverId = "driver-id",
    TruckId = "truck-id",
    Sequence = 1,
    ScheduledDate = "2026-03-15"
});

// Lifecycle transitions
await klau.Jobs.StartAsync(newJob.Id);    // ASSIGNED -> IN_PROGRESS
await klau.Jobs.CompleteAsync(newJob.Id);  // IN_PROGRESS -> COMPLETED

Storefront (Online Ordering)

Storefronts are configured through the API and live at {slug}.rolloff.app. Public endpoints (catalog, order submission, availability) do not require authentication.

// Get storefront catalog (public, no auth required)
var config = await klau.Storefronts.GetConfigAsync("my-company");
foreach (var offering in config.ServiceOfferings)
    Console.WriteLine($"{offering.Name}: ${offering.BasePriceCents / 100.0}");

// Check available delivery dates
var availability = await klau.Storefronts.CheckAvailabilityAsync("my-company",
    new CheckAvailabilityRequest { Zip = "90210" });

// Submit an order
var order = await klau.Storefronts.SubmitOrderAsync("my-company", new SubmitOrderRequest
{
    ServiceOfferingId = config.ServiceOfferings[0].Id,
    Contact = new OrderContact
    {
        Name = "Jane Doe",
        Phone = "5555551234",
        Email = "jane@example.com"
    },
    DeliveryAddress = new DeliveryAddress
    {
        Street = "123 Main St",
        City = "Anytown",
        State = "CA",
        Zip = "90210"
    },
    RequestedDeliveryDate = "2026-03-15",
    TimeWindow = TimeWindow.MORNING
});

// Track the order (public, no auth)
var tracking = await klau.Orders.GetStatusAsync(order.OrderId);
Console.WriteLine($"Order {tracking.OrderId}: {tracking.Status}");

Dump Tickets and Settlement

// Record a dump ticket
var ticket = await klau.DumpTickets.CreateAsync(new CreateDumpTicketRequest
{
    JobId = "job-id",
    TicketNumber = "DT-2026-0042",
    GrossWeightLbs = 12000,
    TareWeightLbs = 8000
});

// Verify with corrections
await klau.DumpTickets.VerifyAsync(ticket.Id, new VerifyDumpTicketRequest
{
    GrossWeightLbs = 12500  // corrected weight
});

// Manual settlement (auto-settlement happens via dump ticket capture)
var settlement = await klau.Orders.SettleAsync("order-id");
Console.WriteLine($"Settled: ${settlement.FinalTotalCents / 100.0}");

Customers

// Search customers
var customers = await klau.Customers.ListAsync(search: "Acme");

// Create a customer
var customer = await klau.Customers.CreateAsync(new CreateCustomerRequest
{
    Name = "Acme Construction",
    ContactName = "John Doe",
    ContactPhone = "5555551234",
    ContactEmail = "john@example.com"
});

// Get full customer 360 view
var view = await klau.Customers.Get360Async(customer.Id);
Console.WriteLine($"Health: {view.HealthScore} | Orders: {view.TotalOrders}");

// List customer's sites
var sites = await klau.Customers.ListSitesAsync(customer.Id);

Materials

// List active materials
var materials = await klau.Materials.ListAsync(activeOnly: true);

// Seed from industry templates
var templates = await klau.Materials.ListTemplatesAsync();
await klau.Materials.SeedFromTemplateAsync(
    templates.Select(t => t.Code).ToList());

Company Settings

Read and update your company's operational configuration. Useful for setting up container sizes, operating hours, and import mappings before running bulk imports.

// Read current settings
var company = await klau.Company.GetAsync();
Console.WriteLine($"Container sizes: {string.Join(", ", company.ContainerSizes)}");
Console.WriteLine($"Hours: {company.WorkdayStart}–{company.WorkdayEnd} {company.Timezone}");
Console.WriteLine($"Workdays: {string.Join(", ", company.Workdays)}");

// Add a nonstandard container size (e.g. 35-yard)
var updated = await klau.Company.UpdateAsync(new UpdateCompanyRequest
{
    ContainerSizes = [10, 15, 20, 30, 35, 40]
});

// Configure import mappings (map your ERP service codes to Klau job types)
await klau.Company.UpdateAsync(new UpdateCompanyRequest
{
    ImportServiceCodeMappings =
    [
        new ServiceCodeMapping { ExternalCode = "DEL", KlauJobType = "DELIVERY" },
        new ServiceCodeMapping { ExternalCode = "PU",  KlauJobType = "PICKUP" },
        new ServiceCodeMapping { ExternalCode = "SW",  KlauJobType = "SWAP" },
        new ServiceCodeMapping { ExternalCode = "RLO", KlauJobType = "SKIP" }  // skip relay-only codes
    ]
});

// Adjust dispatch automation
await klau.Company.UpdateAsync(new UpdateCompanyRequest
{
    AutoPublishDispatches = true,
    DispatchApprovalThreshold = 80  // auto-publish plans scoring 80+
});

Error Handling

All API errors throw KlauApiException with structured properties for programmatic routing:

try
{
    await klau.Jobs.GetAsync("nonexistent-id");
}
catch (KlauApiException ex) when (ex.IsRateLimit)
{
    // Wait and retry — RetryAfter is parsed from the Retry-After header
    await Task.Delay(ex.RetryAfter ?? TimeSpan.FromSeconds(5));
}
catch (KlauApiException ex) when (ex.IsValidation)
{
    // Field-level validation errors are parsed into typed objects
    foreach (var err in ex.ValidationErrors)
        Console.WriteLine($"  {err.Field}: {err.Message}");
}
catch (KlauApiException ex) when (ex.IsNotFound)
{
    Console.WriteLine($"Resource not found: {ex.Message}");
}
catch (KlauApiException ex)
{
    Console.WriteLine($"Error: {ex.ErrorCode} - {ex.Message} (HTTP {ex.StatusCode})");
}

Convenience properties: IsRateLimit, IsNotFound, IsValidation, IsUnauthorized, IsInsufficientScope, IsConflict. The RetryAfter property (TimeSpan?) is available on rate limit errors. ValidationErrors (IReadOnlyList<ValidationDetail>) provides typed field/message/constraint details.

Common error codes: VALIDATION_ERROR, UNAUTHORIZED, NOT_FOUND, INSUFFICIENT_SCOPE, COMMAND_FAILED.

The SDK automatically retries transient errors (429, 502, 503, 504) with exponential backoff up to 3 times, and respects Retry-After headers from rate limiting.

Pagination

Use ListAllAsync to iterate all results without manual paging. The SDK handles page advancement automatically:

// Recommended — zero boilerplate
await foreach (var job in klau.Jobs.ListAllAsync(date: "2026-03-15"))
    Console.WriteLine($"{job.Type} - {job.CustomerName}");

// With LINQ (requires System.Linq.Async NuGet package)
var urgent = await klau.Jobs.ListAllAsync(date: "2026-03-15")
    .Where(j => j.Priority == JobPriority.URGENT)
    .ToListAsync();

ListAllAsync is available on Jobs, Customers, Drivers, Trucks, Yards, DumpSites, and DumpTickets.

For page-level control, use ListAsync which returns PagedResult<T>:

var page1 = await klau.Jobs.ListAsync(page: 1, pageSize: 50);
Console.WriteLine($"Showing {page1.Items.Count} of {page1.Total}");

if (page1.HasMore)
{
    var page2 = await klau.Jobs.ListAsync(page: 2, pageSize: 50);
}

API Key Scopes

API keys can be scoped to specific permissions using action:resource format:

Scope Access
read:all Read access to all resources
write:all Write access to all resources
read:jobs Read jobs only
write:dispatches Write access to dispatches
* Full access (read + write, all resources)

Resources: jobs, drivers, trucks, dispatches, customers, sites, yards, dump-sites, materials, storefronts, orders, dump-tickets, communications, intelligence, coaching, export, and more.

If a request exceeds the key's scopes, the API returns 403 INSUFFICIENT_SCOPE.

ASP.NET Core Integration

Dependency injection

// Minimal — reads API key from KLAU_API_KEY env var
builder.Services.AddKlauClient();

// From configuration
builder.Services.AddKlauClient(opts =>
    builder.Configuration.GetSection("Klau").Bind(opts));

// Inject via interface
public class DispatchService(IKlauClient klau)
{
    public async Task SyncJobs() =>
        await foreach (var job in klau.Jobs.ListAllAsync(date: "2026-03-15"))
            // ...
}

Health checks

builder.Services.AddHealthChecks()
    .AddKlauCheck();  // calls Company.GetAsync to verify connectivity

OpenTelemetry tracing

Every API call emits a span via System.Diagnostics.ActivitySource. Tags include http.request.method, url.path, http.response.status_code, klau.tenant.id, and klau.retry.count.

builder.Services.AddOpenTelemetry()
    .WithTracing(b => b.AddSource("Klau.Sdk"));

Idempotency

Use KlauRequestOptions to pass idempotency keys on mutation methods. This prevents duplicate creates when ERP queue messages are delivered more than once:

await klau.Jobs.CreateAsync(request, new KlauRequestOptions
{
    IdempotencyKey = $"erp-order-{workOrder.ExternalId}",
    Timeout = TimeSpan.FromSeconds(60),  // per-request timeout override
});

Testing

All domain clients have interfaces (IJobClient, IDispatchClient, etc.) for business-level mocking:

var mockJobs = Substitute.For<IJobClient>();
mockJobs.ListAsync(date: "2026-03-15").Returns(
    KlauModelFactory.PagedResult([
        KlauModelFactory.Job(status: JobStatus.ASSIGNED, driverId: "drv-1"),
        KlauModelFactory.Job(status: JobStatus.UNASSIGNED),
    ]));

var service = new YourService(mockJobs);

KlauModelFactory provides static factories with sensible defaults for Job, DispatchBoardJob, Customer, Driver, Truck, Yard, DumpSite, and PagedResult<T>.

For HTTP-level testing, the test helpers include MockHttpHandler — see tests/.

Raw API Access

Call endpoints the SDK doesn't cover yet through the full auth/retry infrastructure:

var response = await klau.SendRawAsync(
    HttpMethod.Get, "api/v1/analytics/route-efficiency?date=2026-03-16");
var json = await response.Content.ReadAsStringAsync();

Examples

Example Description
EnterpriseSimulator Start here. Full enterprise pipeline: CSV export → import → optimize → export dispatch plan
CsvJobImport Minimal CSV import: parse → import → optimize → read assignments
WebhookIntegration Kestrel web app: bidirectional sync with webhooks

License

MIT

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.8.0 121 4/7/2026
0.7.2 116 4/6/2026
0.7.1 112 4/5/2026
0.7.0 123 4/2/2026
0.6.0 115 4/2/2026
0.5.0 125 3/16/2026
0.4.0 112 3/16/2026
0.3.0 124 3/16/2026
0.2.0 125 3/16/2026
0.1.0 123 3/16/2026