Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

.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
1
dotnet add package ScaiLabs.ScaiVault

Targets .NET 8+.

Authenticate#

csharp
1
2
3
4
5
6
7
using ScaiVault.Sdk;

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

Client credentials (auto-refresh):

csharp
1
2
3
4
5
6
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
1
2
3
4
5
6
7
8
9
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
1
2
3
4
5
6
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#

Updated 2026-05-17 13:26:51 View source (.md) rev 2