---
title: ".NET SDK — Anis Developers"
description: "The Anis Partner SDK for .NET 8 and .NET 10 — install, register, call, handle refusals and observe."
url: https://developers.anis.ly/sdks/dotnet/
language: en
---

> Every page of this documentation: https://developers.anis.ly/llms.txt

# .NET SDK

`Anis.Partners.Sdk` — for **.NET 8** and **.NET 10**.

```bash
dotnet add package Anis.Partners.Sdk
```

## Register it

```csharp
builder.Services
    .AddAnisPartners(builder.Configuration.GetSection("AnisPartners"))
    .WithSigner(EcdsaP256Signer.FromPemFile("/secure/partner-key.pem", keyId));
```

```json
{
  "AnisPartners": {
    "Authority": "https://<the address Anis gave you>",
    "SignatureLifetime": "00:01:00",
    "AcceptLanguage": "English",
    "SigningKeyCacheDuration": "00:10:00",
    "Timeout": "00:00:30"
  }
}
```

Then take `IAnisPartnersClient` from the container. The settings are checked when your application starts: a missing address, or a signature lifetime outside 1 to 60 seconds, stops it there.

**The signer is a separate step on purpose** — there is no safe default for where your private key lives. `EcdsaP256Signer` signs with a key file; for a vault or hardware module, implement `IRequestSigner` — your key id, and a method that signs bytes and returns the 64-byte signature — and the key never enters your process.

## Use it

```csharp
var profile = await anis.Profile.GetAsync(ct);

await foreach (var wallet in anis.Wallets.ListAsync(ct))       // pages followed for you
    Console.WriteLine($"{wallet.Name} {wallet.Balance}");

var cards = await anis.Catalogue.ListCardsAsync(walletId, subcategoryId, cancellationToken: ct);

var credential = await anis.OwnedCards.RevealAsync(walletId, soldCardId, ct);
```

## Orders

```csharp
var operationId = Guid.NewGuid();                  // yours: store it with the request BEFORE sending

var outcome = await anis.Orders.CreateAsync(walletId, operationId, new CreateOrderRequest
{
    CardId            = card.Id,
    Quantity          = 2,
    ExpectedUnitPrice = card.UnitPrice!.Value,
    ExpectedTotal     = card.UnitPrice!.Value.Multiply(2),   // exact decimal
}, ct);

switch (outcome)
{
    case OrderCompleted completed:   await vault.StoreAsync(completed.Credentials, ct); break;  // store FIRST
    case OrderProcessing processing: await ResumeAfterAsync(operationId, processing.RetryAfter); break;
    case OrderReplayed replayed:     await EnsureStoredAsync(replayed.Order, ct); break;
        // sent earlier: if you never stored them (the first answer was lost), reveal by replayed.Order.InvoiceId
}
```

`ResumeAsync` sends the same order again under the same id — the way to recover. See [Orders and recovery](https://developers.anis.ly/docs/orders-and-recovery.md) for the full rules and the error handling.

## Refusals

Every refusal is an `AnisApiException` (or a subclass for the cases you handle differently) carrying `Code`, `RequestId`, `RetryAfter`, `IsReplayed` and — on an order — `OrderOutcome`: `NotPlaced` or `Unknown`. Each [error page](https://developers.anis.ly/errors.md) names its exception.

Three other exceptions mean something different:

| Exception | Means | On an order |
| --- | --- | --- |
| `UnverifiableResponseException` | An answer could not be shown to come from Anis; it was thrown away | Resume with the same id |
| `RequestSigningException` | Your signer failed; nothing was sent | Send it again once the signer works |
| `TaskCanceledException`, `HttpRequestException` | Timed out, or the connection failed | Resume with the same id |

## Several Anis applications in one host

```csharp
builder.Services.AddAnisPartners("brand-a", config.GetSection("AnisPartners:BrandA")).WithSigner(signerA);
builder.Services.AddAnisPartners("brand-b", config.GetSection("AnisPartners:BrandB")).WithSigner(signerB);

var brandA = factory.GetClient("brand-a");   // IAnisPartnersClientFactory, or [FromKeyedServices("brand-a")]
```

Each application has its own pipeline and key, and its traces and metrics carry `anis.client`.

> **No automatic retries on this client**
>
> A retry policy added to every HTTP client (for example the .NET Aspire service defaults) gives up on each attempt after 10 seconds by default, sooner than the SDK’s 30. A slow order can then complete at Anis while its answer — with the card codes — is thrown away. Retry reads yourself if you like; recover orders with `ResumeAsync`.

## Observability

Traces and metrics under `Anis.Partners.Sdk`; logs through your own logging. See [Observability](https://developers.anis.ly/docs/observability.md) for the metric names and what to alert on.

## If your host replaces .NET’s built-in container

The SDK uses .NET 8 keyed services. A third-party container must support them — Autofac 9 or later, Lamar 12.1 or later, and SimpleInjector all do. Otherwise build `AnisPartnersClient` directly with an `HttpClient` that carries a `PartnerVerifyingHandler` around a `PartnerSigningHandler`.
