.NET SDK
Idiomatic C# client using HttpClient. Async-only (no sync wrappers — await it). Typed models with System.Text.Json source-generated converters.
Install
| 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | 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
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
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
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
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
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
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
| 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:
| 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