---
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 ScaiLabs.ScaiVault
```

Targets .NET 8+.

## Authenticate

```csharp
using ScaiVault.Sdk;

var client = new ScaiVaultClient(new ScaiVaultClientOptions
{
    BaseUrl = "https://scaivault.scailabs.ai",
    Token = Environment.GetEnvironmentVariable("SCAIVAULT_TOKEN"),
});
```

Client credentials (auto-refresh):

```csharp
var client = new ScaiVaultClient(new ScaiVaultClientOptions
{
    BaseUrl = "https://scaivault.scailabs.ai",
    ClientId = Environment.GetEnvironmentVariable("SCAIKEY_CLIENT_ID"),
    ClientSecret = Environment.GetEnvironmentVariable("SCAIKEY_CLIENT_SECRET"),
});
```

`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
};
```

## Dependency injection

Typical ASP.NET Core pattern:

```csharp
builder.Services.AddSingleton(sp => new ScaiVaultClient(new ScaiVaultClientOptions
{
    BaseUrl = builder.Configuration["ScaiVault:BaseUrl"],
    ClientId = builder.Configuration["ScaiVault:ClientId"],
    ClientSecret = builder.Configuration["ScaiVault:ClientSecret"],
}));
```

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.
