TenaVault
Book a demo
Documentation

Reads in microseconds. Writes with a seal.

Everything here matches the real SDK and the real API — the quickstart, the options table, and every curl example are runnable as written.

Quickstart

Install the SDK, then boot a client scoped to one org and one tenant:

npm install @tenavault/sdk
import { TenaVault } from '@tenavault/sdk';

const vault = await TenaVault.init({
  baseUrl: 'https://api.tenavault.com',
  org: 'acme',
  tenant: 'meridian-capital',
  sdkKey: process.env.TV_SDK_KEY!,
  stream: true, // push updates over SSE — no polling
});

const limit = vault.get('limits.api_rate_per_min', 500); // fallback if unset
// stream: true above already keeps this in sync — no polling loop to write

Get an sdkKey from your org's Settings → SDK keys in the dashboard — it's a delivery-only credential, scoped to reads, separate from a user's session.

Concepts

Scopes & resolution order. A key or config document can be set at up to four scopes: global, environment, template, tenant. Resolving a value for a tenant walks those in order and returns the most specific layer that's actually set — tenant beats template beats environment beats global. This is the one rule every read in TenaVault follows, no exceptions.

Snapshots. Every SDK read is served from an in-memory snapshot fetched once at init() — there's no network call on the read path. A snapshot is a content-addressed, hashed bundle: { org, tenant, environment, hash, generatedAt, config }.

Sealing. A change to governed config only takes effect once it's proposed as a change request, collects the approvals your org's policy requires, and is merged — see Changes & seals.

SDK reference (Node / TypeScript)

TenaVault.init(options): Promise<TenaVault> boots the client and throws if the initial snapshot fetch fails. Options:

baseUrl
string, required
Your API host, e.g. https://api.tenavault.com — no trailing slash.
org / tenant
string, required
Which org and tenant this client resolves config for.
sdkKey
string, required
The delivery credential from Settings → SDK keys.
environment
string, optional
Scope reads to a specific environment layer.
refreshIntervalMs
number, optional
Poll for updates on an interval; omit to refresh manually via refresh().
stream
boolean, optional
Hold an SSE connection and apply changes the moment they happen — no polling needed.
streamRetryMs
number, default 5000
Delay before reconnecting a dropped stream.
fetchFn
typeof fetch, optional
Injectable for tests; defaults to the global fetch.

vault.get<T>(key, fallback?) — a local, in-memory read. Missing keys return the fallback (or undefined).

vault.all() — the full resolved config as a read-only object.

vault.hash — the current snapshot's content hash; changes iff the config does.

vault.refresh(): Promise<boolean> — revalidate against the delivery plane (via ETag / If-None-Match). Returns whether new config was applied. A network failure or 5xx keeps serving the last known good snapshot — TenaVault being unreachable must never break your app.

vault.close() — stop background polling and streaming.

REST API — the delivery plane

Every SDK, and anything you build yourself in another language, ultimately talks to three endpoints. Every request needs Authorization: Bearer <sdkKey>.

GET /api/delivery/{org}/{tenant} — the pointer. Optionally add ?environment=. Returns 200 with the current snapshot and an ETag header, or 304 if your If-None-Match matches the current hash.

curl https://api.tenavault.com/api/delivery/acme/meridian-capital \
  -H "Authorization: Bearer $TV_SDK_KEY"

# → 200
# {
#   "org": "acme", "tenant": "meridian-capital",
#   "hash": "a3f9c21", "generatedAt": "2026-07-14T09:12:03Z",
#   "config": { "limits.api_rate_per_min": 2000, ... }
# }

GET /api/delivery/{org}/{tenant}/{hash} — an immutable, content-addressed snapshot, served with Cache-Control: public, max-age=31536000, immutable. Fetch this instead of the pointer once you already know the hash you want (from a webhook, or from the events stream below) — it skips revalidation entirely and is safe to cache on a CDN forever.

GET /api/delivery/{org}/{tenant}/events — a Server-Sent Events stream. Each frame is pushed the instant this tenant's effective config changes:

data: {"hash":"b7e12f4"}

A differing hash means "go refetch the pointer." Reconnect on a dropped stream — the SDK does this for you automatically when you pass stream: true.

Only Node/TypeScript ships as a maintained SDK today. Everything above works from any language that can make an HTTP request and hold a connection open.