Build a Custom Integration with the SDK

Master the TypeScript SDK with authentication strategies, document management, search, batch operations, and HMAC signing.

advanced20 min read

What You'll Build

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:

  • Authenticate with email/password login or an API key
  • Create, read, update, and delete documents programmatically
  • Run full-text and semantic (AI-powered) searches
  • Execute batch operations across large document sets efficiently
  • Enable HMAC request signing to protect mutating operations
  • Handle token refresh gracefully without session interruptions
  • Catch and respond to every SDK error type correctly

All 25 SDK resources are listed at the end of the guide for quick reference.

Prerequisites

  • Node.js 18 or later (ESM or CommonJS both work)
  • An npm account or access to the @lifestreamdynamics registry scope
  • A Lifestream Vault account (any tier)
  • For semantic search: Pro tier or higher

Install the SDK

Install the SDK from the npm registry. It ships with bundled TypeScript declarations — no separate @types/ package is required.

bash
npm install @lifestreamdynamics/vault-sdk

The package exports a single named class — LifestreamVaultClient — plus all resource types. There is no default export.

import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';

Authenticate with Login

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.

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

Authenticate with API Keys

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.

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

Manage Documents Programmatically

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 unchanged
  • There is no separate create() or update() method — put() handles both
  • All methods use positional arguments: (vaultId, docPath, ...)
  • Paths must end in .md, use forward slashes, and must not contain .. segments
typescript
// 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);

Batch Operations

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.

typescript
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

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.

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

Token Refresh

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.

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

Error Handling

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:

ClassHTTP statusWhen thrown
SDKErroranyBase class for all SDK errors
ValidationError400Invalid request body or parameters
AuthenticationError401Bad credentials, expired token, missing API key
AuthorizationError403Insufficient scope, wrong tier, vault access denied
NotFoundError404Document, vault, or resource does not exist
ConflictError409Slug already taken, duplicate resource
RateLimitError429Too many requests
NetworkErrorNetwork connectivity failure
typescript
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
  }
}

Complete SDK Resource Reference

The LifestreamVaultClient exposes 25 resource groups, each accessible as a property on the client instance. All resources are fully typed with request/response interfaces.

ResourcePropertyDescription
Vaultsclient.vaultsCreate, list, update, and delete document vaults
Documentsclient.documentsFull CRUD (put, get, delete, move, copy) for Markdown documents
Searchclient.searchFull-text, semantic (pgvector), and hybrid search
AIclient.aiChat sessions, context queries, streaming AI completions
Sharesclient.sharesShareable document links with optional password and expiry
Publishclient.publishPer-document publishing with SEO metadata
Publish Vaultclient.publishVaultEnable/disable vault-level public visibility
Custom Domainsclient.customDomainsRegister and verify custom domains (Business tier)
Webhooksclient.webhooksCreate and manage outbound webhook endpoints
Hooksclient.hooksInternal event hooks (auto-tag, template, etc.)
Connectorsclient.connectorsGoogle Drive, Dropbox, OneDrive sync connectors
Calendarclient.calendarDue dates, agenda, iCal feed, heatmap, calendar connectors
Bookingclient.bookingBooking slots, guest management, rescheduling, event templates
Team Booking Groupsclient.teamBookingGroupsRound-robin scheduling for teams (Business)
Analyticsclient.analyticsVault and booking analytics (Business tier)
API Keysclient.apiKeysCreate, list, and revoke API keys
Subscriptionclient.subscriptionRead and manage the user's subscription tier
Userclient.userProfile, display name, avatar, password, email, data export
Teamsclient.teamsTeam CRUD, membership, invitations
MFAclient.mfaTOTP setup, passkey management, backup codes
SAMLclient.samlSSO configuration (Business tier)
SCIMclient.scimSCIM 2.0 provisioning (nullable, needs scimToken)
Pluginsclient.pluginsPlugin/extension registry management
Collaborationclient.collaborationReal-time collaborative editing (Yjs)
Adminclient.adminHealth check, audit logs, user management (admin only)

What's Next

You now have a solid foundation for building production-grade integrations with the Lifestream Vault SDK. Here are some natural next steps: