Browse, compare, pin, and restore document versions — every save is preserved automatically so you can diff any two snapshots or roll back to any point in history.
Version history gives you a complete audit trail for every document in your vault. Every time you save a document — whether through the editor, the API, WebDAV, or a connector sync — a new version is created automatically. You can browse the full history, read any past snapshot, compare any two versions, pin milestones to prevent cleanup, and restore the document to any earlier state.
By the end of this guide you will be able to:
Available on all tiers. Version history is enabled for every document on every subscription tier — no upgrade required. Versions are created automatically on every document save; there is nothing to configure. HMAC request signing applies only to API-key consumers of mutating document operations — JWT sessions (including login()) are exempt from requireSignature and never sign requests.
To explore the history of a document, start by listing all of its versions. The response includes each version's number, creation timestamp, and pin status — giving you a full timeline of every save.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
'you@example.com',
'your-password',
);
const VAULT_ID = 'vault-uuid';
const DOC_PATH = 'notes/my-doc.md';
// listVersions returns DocumentVersion[] directly (no wrapper object)
const versions = await client.documents.listVersions(VAULT_ID, DOC_PATH);
console.log(`Found ${versions.length} versions:`);
for (const v of versions) {
const pinned = v.isPinned ? ' [PINNED]' : '';
console.log(` v${v.versionNum} — ${v.createdAt}${pinned}`);
}Once you know which version you want to inspect, you can fetch its full Markdown content by version number. This is a read-only operation — it does not modify the document.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
'you@example.com',
'your-password',
);
// getVersion returns a DocumentVersionWithContent directly (no wrapper object)
const version = await client.documents.getVersion(
'vault-uuid',
'notes/my-doc.md',
3, // version number
);
console.log('Version number:', version.versionNum);
console.log('Created at:', version.createdAt);
console.log('Pinned:', version.isPinned);
console.log('Content length:', version.content?.length ?? 0, 'characters');
console.log('\nContent preview:');
console.log((version.content ?? '').slice(0, 200));The diff endpoint compares any two versions of a document and returns a unified diff string — the same format used by git diff. Pass from and to version numbers in the request body. You can diff non-adjacent versions to see the cumulative change across multiple saves.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
'you@example.com',
'your-password',
);
// diffVersions takes positional version numbers: (vaultId, docPath, from, to)
const result = await client.documents.diffVersions(
'vault-uuid',
'notes/my-doc.md',
1, // from version
3, // to version
);
console.log(`Diff from v${result.fromVersion} to v${result.toVersion}:`);
// 'changes' is an array of diff hunks ({ value, added?, removed? })
for (const change of result.changes) {
const marker = change.added ? '+' : change.removed ? '-' : ' ';
process.stdout.write(`${marker} ${change.value}`);
}Lifestream Vault runs a version-pruning worker periodically to remove old unpinned versions and keep storage manageable. Pinned versions are never pruned — they are preserved indefinitely regardless of how many newer versions accumulate.
Use pinning to protect milestone snapshots: a completed draft, the version you shared with a client, or the state of a document before a major rewrite.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
const { client } = await LifestreamVaultClient.login(
'https://vault.lifestreamdynamics.com',
'you@example.com',
'your-password',
);
const VAULT_ID = 'vault-uuid';
const DOC_PATH = 'notes/my-doc.md';
// pinVersion / unpinVersion return the updated DocumentVersion directly
const pinned = await client.documents.pinVersion(VAULT_ID, DOC_PATH, 3);
console.log(`v${pinned.versionNum} is now pinned: ${pinned.isPinned}`); // true
// Unpin it later if it no longer needs protection
const unpinned = await client.documents.unpinVersion(VAULT_ID, DOC_PATH, 3);
console.log(`v${unpinned.versionNum} is now pinned: ${unpinned.isPinned}`); // falsePin milestone versions — such as launch drafts, major rewrites, or versions shared externally — so they survive automated pruning. A pinned version is always retrievable by number, no matter how much time passes or how many newer versions accumulate.
Restoring a version is non-destructive: the API copies the content of the selected historical version into a brand-new version at the head of the document's history. Your current version and all intermediate versions remain intact — restoration is always reversible.
Restore is HMAC-signed for API-key consumers. The requireSignature middleware enforces cryptographic signing on mutating document operations to prevent replay attacks — but it applies only to API-key authentication, where the API key itself is the signing secret. JWT sessions (e.g. from login()) are exempt and pass through unsigned. When authenticating with an API key, set enableRequestSigning: true and the SDK signs automatically. For cURL, you must compute and include the X-Signature, X-Signature-Timestamp, and X-Signature-Nonce headers manually — see the HMAC signing documentation for the algorithm.
import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
// Signing applies to API-key auth (the API key is the signing secret).
// A JWT session from login() is exempt and works without enableRequestSigning.
const client = new LifestreamVaultClient({
baseUrl: 'https://vault.lifestreamdynamics.com',
apiKey: process.env.VAULT_API_KEY!,
enableRequestSigning: true, // the SDK signs the restore request automatically
});
// Restore the document to the content from version 3.
// This creates a new version (e.g. v5) containing v3's content — v4 is preserved.
// restoreVersion returns the updated Document directly (no wrapper object).
const document = await client.documents.restoreVersion(
'vault-uuid',
'notes/my-doc.md',
3, // version number to restore from
);
console.log('Document restored successfully.');
console.log('Document ID:', document.id);
console.log('Document path:', document.path);
console.log('Document title:', document.title);Lifestream Vault manages version storage automatically through the version-pruning BullMQ worker, which runs on a periodic schedule in the background.
How pruning works:
version-pruning worker scans documents for old, unpinned versions that exceed the configured retention thresholdVERSION_RETENTION_COUNT and VERSION_RETENTION_DAYS environment variables.What this means in practice:
Pin important milestones before they get pruned. Once a version is pruned it is gone. If you have a version you may want to reference later — a completed draft, a pre-refactor snapshot, a version sent to a reviewer — pin it immediately.
Use diff before restoring. Restoration is non-destructive (it creates a new version), but it is still cleaner to confirm what you are restoring. Diff the candidate version against the current head before calling restore.
Version numbers are sequential and represent save order. Version 1 is the first save, version 2 is the next, and so on. Numbers are never reused, even after restoration.
Enable HMAC request signing in the SDK for restore operations. Construct the client with enableRequestSigning: true to have the SDK sign all mutating requests automatically:
const client = new LifestreamVaultClient({
baseUrl: 'https://vault.lifestreamdynamics.com',
apiKey: process.env.VAULT_API_KEY,
enableRequestSigning: true,
});
document.created and document.updated events to trigger hooks whenever a new version is saved