JavaScript SDK
Modern TypeScript/JavaScript client using native fetch. Typed with full type inference, zero runtime deps on browsers, minimal deps on Node.
Install
| npm install @scaivault/sdk
|
Requires Node 18+ (for native fetch) or a modern browser. Latest release: 1.8.0. The packed tarball is also published at https://www.scailabs.ai/downloads under "ScaiVault SDKs".
Don't use the JavaScript SDK in browser code. Bundling ScaiVault tokens into a frontend exposes them to anyone who opens DevTools. Browsers should call your own backend, which in turn calls ScaiVault.
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 | import { ScaiVaultClient } from "@scaivault/sdk";
// Either works — the SDK detects the credential type.
const client = new ScaiVaultClient({
baseUrl: "https://scaivault.scailabs.ai",
token: process.env.SCAIVAULT_TOKEN, // JWT or "key_…" API key
});
// Or explicit:
const client = new ScaiVaultClient({
baseUrl: "https://scaivault.scailabs.ai",
apiKey: process.env.SCAIVAULT_API_KEY, // always sent as X-API-Key
});
|
client.setToken(...) and client.setApiKey(...) swap credentials at runtime; setToken auto-detects the key_ prefix.
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 | // Read
const secret = await client.secrets.read("environments/production/salesforce/oauth");
secret.version; // number
secret.data; // Record<string, unknown>
secret.metadata; // SecretMetadata
secret.secret_type; // 'kv' | 'json' | 'certificate' | 'ssh_key' | 'api_key'
// Write
await client.secrets.write(
"environments/production/salesforce/oauth",
{ client_id: "...", client_secret: "..." },
{
secretType: "json",
customMetadata: { tags: ["salesforce"] },
},
);
// Update (metadata only)
await client.secrets.update(
"app/db/password",
undefined,
{ customMetadata: { tags: ["critical"] } },
);
// Delete
await client.secrets.delete("old/secret");
await client.secrets.delete("old/secret", { permanent: true });
// List
const listing = await client.secrets.list({
prefix: "environments/production/",
limit: 50,
});
for (const item of listing.data) {
console.log(item.path, item.version);
}
// Paginate
let cursor = listing.cursor;
while (listing.has_more) {
const next = await client.secrets.list({ prefix: "...", cursor });
cursor = next.cursor;
}
// Specific version
const v1 = await client.secrets.read("app/db/password", { version: 1 });
// Rotate
const rotated = await client.secrets.rotate("app/db/password", {
reason: "compromise",
newValue: { password: "..." },
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 | // Create
const policy = await client.policies.create(
"production-read-only",
[
{
pathPattern: "environments/production/**",
permissions: ["read", "list"],
conditions: {
ipRanges: ["10.0.0.0/8"],
requireMfa: true,
},
},
],
{ description: "Developers read production from VPN + MFA" },
);
// Bind
await client.policies.bind(policy.id, {
identityType: "group",
identityId: "group:developers",
});
// Test
const result = await client.policies.test({
identityId: "user:alice@acme.example",
path: "environments/production/salesforce/oauth",
permission: "read",
context: { source_ip: "10.0.1.50" },
});
console.log(result.allowed);
|
Rotation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | const policy = await client.rotation.create({
name: "quarterly",
interval: "90d",
gracePeriod: "48h",
warnBefore: "7d,1d",
autoGenerate: false,
});
await client.rotation.assignSecret(policy.id, "environments/production/salesforce/oauth");
await client.rotation.triggerRotation(policy.id, [
"environments/production/salesforce/oauth",
]);
const history = await client.rotation.getHistory(policy.id, { limit: 100 });
const due = await client.rotation.getSecretsDue({ 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 | // Issue cert
const cert = await client.pki.issue("svc-mtls", {
commonName: "billing.svc.cluster.local",
altNames: ["billing-api.svc.cluster.local"],
ttl: "168h",
});
// cert.certificate, cert.private_key, cert.ca_chain
// CSR workflow
const csr = await client.pki.generateCSR({
subject: { commonName: "vendor.example" },
sanDns: ["vendor-api.example"],
keyType: "ec",
keySize: 256,
});
const imported = await client.pki.importCSR("-----BEGIN CERTIFICATE REQUEST-----\n...");
await client.pki.approveCSR(imported.id);
const signed = await client.pki.signCSRById(imported.id, {
caId: "ca_intermediate",
validityDays: 90,
});
// Validate
const validation = await client.pki.validateCertificate({
certificatePem: "-----BEGIN...",
chainPem: "-----BEGIN...",
checkRevocation: true,
});
|
Dynamic secrets
| const lease = await client.dynamic.generateCredentials("support-db", "readonly", {
ttl: "2h",
});
const connectionUrl = lease.data.connection_url;
try {
// Use creds
} finally {
await client.dynamic.revokeLease(lease.lease_id);
}
|
Error handling
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | import { ScaiVaultError, AccessDeniedError, NotFoundError, RateLimitError } from "@scailabs/scaivault";
try {
const secret = await client.secrets.read("app/db/password");
} catch (e) {
if (e instanceof NotFoundError) {
// ...
} else if (e instanceof AccessDeniedError) {
console.error(e.code, e.details);
} else if (e instanceof RateLimitError) {
await new Promise(r => setTimeout(r, e.retryAfter * 1000));
} else if (e instanceof ScaiVaultError) {
console.error(e.requestId, e.message);
} else {
throw e;
}
}
|
Retries and timeouts
| const client = new ScaiVaultClient({
baseUrl: "https://scaivault.scailabs.ai",
token: "...",
timeout: 10000, // ms
maxRetries: 5,
retryBackoff: 1.5,
});
|
Transient failures (rate-limits, connection errors, 5xx) retry with exponential backoff. Auth and validation failures raise immediately.
What's covered
The JS/TS SDK is at full feature parity with the Python SDK — all 127 API endpoints, audit-verified on every release. Namespaces on client:
| Namespace |
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 |
Environment variables
| Variable |
Default for |
SCAIVAULT_BASE_URL |
baseUrl |
SCAIVAULT_TOKEN |
token (auto-detects key_… API keys) |
SCAIVAULT_API_KEY |
apiKey (explicit) |
new ScaiVaultClient() with no options picks up env.
What's next