---
title: JavaScript SDK
path: sdks/javascript
status: published
---

# JavaScript SDK

Modern TypeScript/JavaScript client using native `fetch`. Typed with full type inference, zero runtime deps on browsers, minimal deps on Node.

## Install

```bash
npm install @scailabs/scaivault
```

Requires Node 18+ (for native `fetch`) or a modern browser.

**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

```typescript
import { ScaiVaultClient } from "@scailabs/scaivault";

const client = new ScaiVaultClient({
  baseUrl: "https://scaivault.scailabs.ai",
  token: process.env.SCAIVAULT_TOKEN,
});
```

Client-credentials (auto-refreshing):

```typescript
const client = new ScaiVaultClient({
  baseUrl: "https://scaivault.scailabs.ai",
  clientId: process.env.SCAIKEY_CLIENT_ID,
  clientSecret: process.env.SCAIKEY_CLIENT_SECRET,
});
```

## Secrets

```typescript
// 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

```typescript
// 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

```typescript
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

```typescript
// 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

```typescript
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

```typescript
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

```typescript
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.

## Environment variables

| Variable | Default for |
|----------|-------------|
| `SCAIVAULT_BASE_URL` | `baseUrl` |
| `SCAIVAULT_TOKEN` | `token` |
| `SCAIKEY_CLIENT_ID` | `clientId` |
| `SCAIKEY_CLIENT_SECRET` | `clientSecret` |

`new ScaiVaultClient()` with no options picks up env.

## What's next

- [Python SDK](./python)
- [.NET SDK](./dotnet)
- [Managing Secrets](../api-guides/secrets) — underlying HTTP API.
