Master the TypeScript SDK with authentication strategies, document management, search, batch operations, and HMAC signing.
The Lifestream Vault SDK is a fully-typed TypeScript client that exposes every API resource as an ergonomic class. You can authenticate, manage documents, run searches, fire webhooks, and sign requests — all from Node.js scripts, serverless functions, or a backend service.
By the end of this guide you will know how to:
All 25 SDK resources are listed at the end of the guide for quick reference.
Prerequisites
@lifestreamdynamics registry scopeInstall the SDK from the npm registry. It ships with bundled TypeScript declarations — no separate @types/ package is required.
npm install @lifestreamdynamics/vault-sdkThe package exports a single named class — LifestreamVaultClient — plus all resource types. There is no default export.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
The LifestreamVaultClient.login() static method handles the full email/password authentication flow. It returns a destructured object containing the ready-to-use client, the access token, and a refresh token you can persist for later sessions.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
// Authenticate — positional args: baseUrl, email, password
const { client, tokens, refreshToken } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
'you@example.com',
'your-password',
);
console.log('Access token expires at:', tokens.expiresAt);
console.log('Refresh token:', refreshToken);
// Use the client for any subsequent call
const vaults = await client.vaults.list();
console.log('Your vaults:', vaults.map((v) => v.name));The SDK automatically refreshes the access token before it expires. You only need to call login() once per session. If you persist the refreshToken, you can reconstruct a client without re-entering credentials — useful for long-running scripts and CI/CD pipelines.
API keys are the preferred credential for server-to-server integrations, CI/CD pipelines, and automation scripts. They carry explicit scopes (read and write) and can optionally be scoped to a single vault.
Generate a key in Settings → API Keys → New Key, or via the API. Keys are prefixed with lsv_k_ and are shown only once — store them immediately in your secrets manager.
Follow the principle of least privilege when creating API keys. Scope each key to only the vaults and operations your integration needs — for example, a CI/CD pipeline that only publishes docs should use a key scoped to a single vault with only write permission.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
// Construct a client directly from an API key — no login() call needed
const client = new LifestreamVaultClient({
baseUrl: 'https://vault.lifestreamdynamics.com',
apiKey: process.env.VAULT_API_KEY, // e.g. lsv_k_abc123...
});
// Verify connectivity (admin endpoint — requires admin role API key)
const health = await client.admin.getHealth();
console.log('API status:', health.status); // 'healthy'
// List documents in a specific vault
const docs = await client.documents.list('vault-uuid-here');
console.log('Document count:', docs.length);Never expose API keys in client-side code. Keys beginning with lsv_k_ should only exist in server environments, environment variables, or secrets managers (e.g. AWS Secrets Manager, Vault by HashiCorp). Rotate compromised keys immediately from Settings → API Keys.
Documents are the core unit in Lifestream Vault. Every document is a Markdown file stored at a path within a vault (e.g. notes/meeting-2026-03-01.md). The SDK exposes put() (upsert), get(), delete(), and move() via client.documents.
Key behaviours:
put() is an upsert — it creates or updates a document; the server skips writes when the SHA-256 hash is unchangedcreate() or update() method — put() handles both(vaultId, docPath, ...).md, use forward slashes, and must not contain .. segments// Create a new document with frontmatter — put() is an upsert
const doc = await client.documents.put(
'vault-uuid',
'projects/q1-plan.md',
`---
title: Q1 Project Plan
tags: [planning, q1-2026]
due: 2026-03-31
---
# Q1 Project Plan
## Goals
- Launch SDK v2
- Migrate to PostgreSQL 17
- Ship booking analytics
`,
);
console.log('Created document ID:', doc.id);
console.log('Document path:', doc.path);Lifestream Vault offers two complementary search modes:
| Mode | Engine | When to use |
|---|---|---|
| Full-text | PostgreSQL websearch_to_tsquery | Keyword searches, exact phrases, tag filtering |
| Semantic | pgvector cosine similarity | Conceptual queries, finding related content, AI-powered workflows |
Full-text search is available on all tiers. Semantic search requires Pro or higher and an active embeddings index (the embedding.worker indexes documents asynchronously after each write).
// Full-text search across all vaults the user can access
const results = await client.search.search({
q: 'quarterly planning objectives',
vault: 'vault-uuid', // optional: restrict to one vault
tags: 'planning', // optional: comma-separated tag filter
limit: 20,
offset: 0,
});
console.log(`Found ${results.total} results`);
for (const hit of results.results) {
console.log('---');
console.log('Path:', hit.path);
console.log('Title:', hit.title);
console.log('Snippet:', hit.snippet); // highlighted snippet
console.log('Score:', hit.rank);
}Semantic search requires a Pro or Business subscription. If called on a free account, the SDK will throw an AuthorizationError with the message "Semantic search requires Pro tier or higher".
The SDK does not expose a dedicated batch endpoint, but you can achieve high throughput by combining concurrency-limited Promise pools with the standard CRUD methods. The server applies a rate limit of 100 requests per minute per user for authenticated REST calls — structure your batches accordingly.
Recommended pattern: use a concurrency limiter (e.g. p-limit) to cap simultaneous in-flight requests. This avoids hitting the rate limit while maximising throughput.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
import pLimit from 'p-limit';
import fs from 'fs/promises';
import path from 'path';
import { glob } from 'glob';
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
process.env.VAULT_EMAIL!,
process.env.VAULT_PASSWORD!,
);
const VAULT_ID = 'vault-uuid';
const SOURCE_DIR = './docs';
const CONCURRENCY = 5; // max simultaneous uploads
const limit = pLimit(CONCURRENCY);
const files = await glob('**/*.md', { cwd: SOURCE_DIR });
console.log(`Uploading ${files.length} files...`);
const results = await Promise.allSettled(
files.map((file) =>
limit(async () => {
const content = await fs.readFile(path.join(SOURCE_DIR, file), 'utf8');
const doc = await client.documents.put(VAULT_ID, file, content);
return { file, docId: doc.id };
}),
),
);
const succeeded = results.filter((r) => r.status === 'fulfilled').length;
const failed = results.filter((r) => r.status === 'rejected').length;
console.log(`Done: ${succeeded} succeeded, ${failed} failed.`);If you receive a 429 Too Many Requests response, the SDK will not automatically retry by default. Wrap your batch loop in a retry helper (e.g. p-retry) or reduce your concurrency limit. The Retry-After header on 429 responses indicates how many seconds to wait.
HMAC request signing adds a cryptographic signature to every mutating request (PUT, POST, DELETE). The server verifies the signature and rejects any request that fails validation or that replays a previously used nonce. This protects against replay attacks and man-in-the-middle tampering on document writes.
Signing is enabled with the enableRequestSigning option when constructing the client. Your HMAC secret must be configured on the server via the SIGNING_SECRET environment variable, and the client reads it from process.env.VAULT_SIGNING_SECRET.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
// Enable HMAC signing — the SDK reads the secret from VAULT_SIGNING_SECRET
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
process.env.VAULT_EMAIL!,
process.env.VAULT_PASSWORD!,
{
enableRequestSigning: true, // activates HMAC-SHA256 on mutating requests
},
);
// All writes are now signed automatically — no further changes needed
const doc = await client.documents.put(
'vault-uuid',
'secure/signed-document.md',
'# Signed Document\n\nThis write was HMAC-signed.',
);
console.log('Signed document created:', doc.id);HMAC signing is optional by default but can be made mandatory for a vault by enabling the requireSignature vault setting. Once enabled, any unsigned write to that vault is rejected with a 403 error. This is recommended for vaults containing sensitive or compliance-critical content.
Access tokens expire after 15 minutes. The SDK handles refresh transparently: when a request returns 401, the SDK automatically uses the refresh token to obtain a new access token and retries the original request. You do not need to manage this yourself in most cases.
The refresh request includes the X-Requested-With header required by the server's CSRF protection layer. Auth endpoints are excluded from the retry loop to prevent infinite loops on invalid credentials.
If you need to manually refresh — for example, when restoring a serialized session — use the pattern below.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
process.env.VAULT_EMAIL!,
process.env.VAULT_PASSWORD!,
);
// You can run long-lived operations without worrying about token expiry.
// The SDK refreshes the token silently in the background as needed.
for (let i = 0; i < 1000; i++) {
await client.documents.get('vault-uuid', `doc-${i}.md`);
// If the token expired mid-loop, the SDK refreshed it automatically
}The refresh token itself has a longer TTL (typically 30 days) and is stored as an httpOnly cookie on the server when using the web UI. In the SDK, it is returned as a string from login() and managed in-memory. If your integration runs longer than the refresh token TTL, call login() again to obtain a fresh session.
The SDK surfaces server errors as typed JavaScript errors. All SDK errors extend the base SDKError class, so you can use a single instanceof check in a catch block or narrow further to specific subtypes.
Error classes exported by the SDK:
| Class | HTTP status | When thrown |
|---|---|---|
SDKError | any | Base class for all SDK errors |
ValidationError | 400 | Invalid request body or parameters |
AuthenticationError | 401 | Bad credentials, expired token, missing API key |
AuthorizationError | 403 | Insufficient scope, wrong tier, vault access denied |
NotFoundError | 404 | Document, vault, or resource does not exist |
ConflictError | 409 | Slug already taken, duplicate resource |
RateLimitError | 429 | Too many requests |
NetworkError | — | Network connectivity failure |
import {
LifestreamVaultClient,
AuthorizationError,
NotFoundError,
RateLimitError,
SDKError,
} from '@lifestreamdynamics/vault-sdk';
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
process.env.VAULT_EMAIL!,
process.env.VAULT_PASSWORD!,
);
try {
const { document: doc } = await client.documents.get(
'vault-uuid',
'notes/does-not-exist.md',
);
console.log('Found:', doc.title);
} catch (err) {
if (err instanceof NotFoundError) {
console.warn('Document not found — creating it...');
await client.documents.put(
'vault-uuid',
'notes/does-not-exist.md',
'# New Document\n',
);
} else if (err instanceof AuthorizationError) {
console.error('Access denied. Check vault permissions.');
} else if (err instanceof RateLimitError) {
console.warn('Rate limited:', err.message);
} else if (err instanceof SDKError) {
console.error('API error:', err.message, '(status:', err.statusCode, ')');
} else {
throw err; // re-throw unknown errors
}
}The LifestreamVaultClient exposes 25 resource groups, each accessible as a property on the client instance. All resources are fully typed with request/response interfaces.
| Resource | Property | Description |
|---|---|---|
| Vaults | client.vaults | Create, list, update, and delete document vaults |
| Documents | client.documents | Full CRUD (put, get, delete, move, copy) for Markdown documents |
| Search | client.search | Full-text, semantic (pgvector), and hybrid search |
| AI | client.ai | Chat sessions, context queries, streaming AI completions |
| Shares | client.shares | Shareable document links with optional password and expiry |
| Publish | client.publish | Per-document publishing with SEO metadata |
| Publish Vault | client.publishVault | Enable/disable vault-level public visibility |
| Custom Domains | client.customDomains | Register and verify custom domains (Business tier) |
| Webhooks | client.webhooks | Create and manage outbound webhook endpoints |
| Hooks | client.hooks | Internal event hooks (auto-tag, template, etc.) |
| Connectors | client.connectors | Google Drive, Dropbox, OneDrive sync connectors |
| Calendar | client.calendar | Due dates, agenda, iCal feed, heatmap, calendar connectors |
| Booking | client.booking | Booking slots, guest management, rescheduling, event templates |
| Team Booking Groups | client.teamBookingGroups | Round-robin scheduling for teams (Business) |
| Analytics | client.analytics | Vault and booking analytics (Business tier) |
| API Keys | client.apiKeys | Create, list, and revoke API keys |
| Subscription | client.subscription | Read and manage the user's subscription tier |
| User | client.user | Profile, display name, avatar, password, email, data export |
| Teams | client.teams | Team CRUD, membership, invitations |
| MFA | client.mfa | TOTP setup, passkey management, backup codes |
| SAML | client.saml | SSO configuration (Business tier) |
| SCIM | client.scim | SCIM 2.0 provisioning (nullable, needs scimToken) |
| Plugins | client.plugins | Plugin/extension registry management |
| Collaboration | client.collaboration | Real-time collaborative editing (Yjs) |
| Admin | client.admin | Health check, audit logs, user management (admin only) |
You now have a solid foundation for building production-grade integrations with the Lifestream Vault SDK. Here are some natural next steps:
lsvaultdocument.created, document.updated, and other document event types<lsv-booking> widget