---
title: .NET SDK
path: sdks/dotnet
status: published
---

# .NET SDK

Idiomatic C# client using `HttpClient`. Async-only (no sync wrappers — `await` it). Typed models with `System.Text.Json` source-generated converters.

## Install

```bash
dotnet add package ScaiVault.Sdk
```

Targets .NET 8+ (and .NET 6 LTS). Latest release: **1.8.0**. The `.nupkg` is also published at <https://www.scailabs.ai/downloads> under "ScaiVault SDKs".

## Authenticate

The SDK accepts both ScaiKey-issued JWTs (`Authorization: Bearer …`) and ScaiVault API keys (`X-API-Key: key_…`). A token whose value starts with `key_` is auto-routed to the `X-API-Key` header; pass it either as `Token` or as the explicit `ApiKey` option.

```csharp
using ScaiVault.Sdk;

// Either works — the SDK detects the credential type.
var client = new ScaiVaultClient(new ScaiVaultClientOptions
{
    BaseUrl = "https://scaivault.scailabs.ai",
    Token = Environment.GetEnvironmentVariable("SCAIVAULT_TOKEN"),    // JWT or "key_…"
});

// Or explicit:
var client = new ScaiVaultClient(new ScaiVaultClientOptions
{
    BaseUrl = "https://scaivault.scailabs.ai",
    ApiKey = Environment.GetEnvironmentVariable("SCAIVAULT_API_KEY"), // always X-API-Key
});
```

`client.SetToken(...)` and `client.SetApiKey(...)` swap credentials at runtime; `SetToken` auto-detects the `key_` prefix.

`ScaiVaultClient` is `IDisposable`. In long-lived services, construct once and hold it; don't make a new one per request.

## Secrets

```csharp
// Read
var secret = await client.Secrets.ReadAsync("environments/production/salesforce/oauth");
Console.WriteLine(secret.Data["client_id"]);
Console.WriteLine(secret.Version);
Console.WriteLine(secret.SecretType);  // SecretType enum

// Write
await client.Secrets.WriteAsync(
    path: "environments/production/salesforce/oauth",
    data: new Dictionary<string, object?>
    {
        ["client_id"] = "...",
        ["client_secret"] = "...",
    },
    secretType: SecretType.Json,
    metadata: new Dictionary<string, object?>
    {
        ["tags"] = new[] { "salesforce" }
    }
);

// Update metadata
await client.Secrets.UpdateMetadataAsync(
    "app/db/password",
    metadata: new Dictionary<string, object?>
    {
        ["tags"] = new[] { "critical" }
    }
);

// Delete
await client.Secrets.DeleteAsync("old/secret");
await client.Secrets.DeleteAsync("old/secret", permanent: true);

// List
var listing = await client.Secrets.ListAsync(
    prefix: "environments/production/",
    limit: 50
);
foreach (var item in listing.Data)
    Console.WriteLine($"{item.Path} v{item.Version}");

// Paginate
while (listing.HasMore)
{
    listing = await client.Secrets.ListAsync(
        prefix: "environments/production/",
        cursor: listing.Cursor
    );
}

// Specific version
var v1 = await client.Secrets.ReadAsync("app/db/password", version: 1);

// Rotate
var rotated = await client.Secrets.RotateAsync("app/db/password", new RotateOptions
{
    Reason = "compromise",
    NewValue = new Dictionary<string, object?> { ["password"] = "new-val" },
    GracePeriod = "1h",
});
```

## Policies

```csharp
var policy = await client.Policies.CreateAsync(
    name: "production-read-only",
    rules: new List<PolicyRule>
    {
        new PolicyRule
        {
            PathPattern = "environments/production/**",
            Permissions = new List<Permission> { Permission.Read, Permission.List },
            Conditions = new PolicyConditions
            {
                IpRanges = new List<string> { "10.0.0.0/8" },
                RequireMfa = true,
            }
        }
    },
    description: "Developers read production from VPN + MFA"
);

// Bind
await client.Policies.BindAsync(
    policyId: policy.Id,
    identityType: "group",
    identityId: "group:developers"
);

// Test
var result = await client.Policies.TestAsync(new PolicyTestRequest
{
    IdentityId = "user:alice@acme.example",
    Path = "environments/production/salesforce/oauth",
    Permission = "read",
});
Console.WriteLine(result.Allowed);
```

## Rotation

```csharp
var policy = await client.Rotation.CreateAsync(new CreateRotationPolicyOptions
{
    Name = "quarterly",
    Interval = "90d",
    GracePeriod = "48h",
    WarnBefore = "7d,1d",
    AutoGenerate = false,
});

await client.Rotation.AssignSecretAsync(policy.Id, "environments/production/salesforce/oauth");

await client.Rotation.TriggerRotationAsync(
    policy.Id,
    new List<string> { "environments/production/salesforce/oauth" }
);

var history = await client.Rotation.GetHistoryAsync(policy.Id, limit: 100);
var due = await client.Rotation.GetSecretsDueAsync(withinHours: 168);
```

## PKI

```csharp
// Issue cert
var cert = await client.PKI.IssueCertificateAsync(new IssueCertificateOptions
{
    CaId = "ca_intermediate_mtls",
    CommonName = "billing.svc.cluster.local",
    San = new List<string> { "billing-api.svc.cluster.local" },
    ValidityDays = 7,
});
// cert.CertificatePem, cert.PrivateKeyPem, cert.ChainPem

// Generate CSR in-vault
var csr = await client.PKI.GenerateCSRAsync(new GenerateCSROptions
{
    Subject = new CSRSubject { CommonName = "vendor.example" },
    SanDns = new List<string> { "vendor-api.example" },
    KeyType = "ec",
    KeySize = 256,
});

// External CSR workflow
var imported = await client.PKI.ImportCSRAsync("-----BEGIN CERTIFICATE REQUEST-----\n...");
await client.PKI.ApproveCSRAsync(imported.Id);
var signed = await client.PKI.SignCSRByIdAsync(
    csrId: imported.Id,
    caId: "ca_intermediate_mtls",
    validityDays: 90
);

// Validate
var validation = await client.PKI.ValidateCertificateAsync(
    certificatePem: "-----BEGIN...",
    chainPem: "-----BEGIN...",
    checkRevocation: true
);
Console.WriteLine(validation.Valid);
```

## Dynamic secrets

```csharp
var lease = await client.Dynamic.GenerateCredentialsAsync(
    engine: "support-db",
    role: "readonly",
    ttl: "2h"
);

try
{
    var connectionUrl = (string)lease.Data["connection_url"];
    // use it
}
finally
{
    await client.Dynamic.RevokeLeaseAsync(lease.LeaseId);
}
```

## Error handling

```csharp
using ScaiVault.Sdk.Exceptions;

try
{
    var secret = await client.Secrets.ReadAsync("app/db/password");
}
catch (NotFoundException)
{
    // path doesn't exist
}
catch (AuthorizationException ex)
{
    Console.Error.WriteLine($"Denied: {ex.Code} — {ex.Message}");
}
catch (RateLimitException ex)
{
    await Task.Delay(TimeSpan.FromSeconds(ex.RetryAfter));
}
catch (ScaiVaultException ex)
{
    Console.Error.WriteLine($"{ex.StatusCode} {ex.Code}: {ex.Message}");
}
```

All exceptions derive from `ScaiVaultException`. `StatusCode`, `Code`, `Message`, `Details`, `RequestId` are available on every one.

## Options

```csharp
new ScaiVaultClientOptions
{
    BaseUrl = "https://scaivault.scailabs.ai",
    Token = "...",
    Timeout = TimeSpan.FromSeconds(10),
    MaxRetries = 5,
    PartnerId = "ptn_acme",   // optional, for partner-admin contexts
    TenantId = "tnt_acme_dev",// optional, for explicit cross-tenant
};
```

## What's covered

The .NET SDK is at full feature parity with the Python SDK — **all 127 API endpoints**, audit-verified on every release. Operation clients exposed on `ScaiVaultClient`:

| Property | What it does |
|---|---|
| `client.Secrets` | CRUD, versioning, batch read, rotate |
| `client.Policies` | Path-pattern policies + bindings + test |
| `client.Rotation` | Rotation policies + secret assignment + history + due |
| `client.PKI` | CA / certificate / CSR / trust-anchor lifecycle + validate |
| `client.ACME` | Let's Encrypt-style account / order / challenge / finalize |
| `client.Dynamic` | Engines / roles / lease management for Postgres, MySQL, etc. |
| `client.Federation` | External backend lifecycle, test, sync |
| `client.Subscriptions` | Webhook + long-poll event subscriptions |
| `client.Webhooks` | Webhook CRUD, test, delivery history |
| `client.Identity` | ScaiKey identity cache (partners, tenants, users, groups) |
| `client.Audit` | Audit-log query + export |
| `client.Batch` | `POST /secrets/batch` multi-read |
| `client.ServiceAccounts` | Service-account CRUD + API-key issuance |
| `client.SecretPolicies` | Generator-based secret policies |

## Dependency injection

Typical ASP.NET Core pattern:

```csharp
builder.Services.AddSingleton(sp => new ScaiVaultClient(new ScaiVaultClientOptions
{
    BaseUrl = builder.Configuration["ScaiVault:BaseUrl"]!,
    ApiKey = builder.Configuration["ScaiVault:ApiKey"],   // or Token
}));
```

Inject `ScaiVaultClient` anywhere you need it.

## What's next

- [Python SDK](./python)
- [JavaScript SDK](./javascript)
- [Managing Secrets](../api-guides/secrets) — HTTP API the SDK wraps.
