Package Exports
- @tenlyr/sdk
Readme
@tenlyr/sdk
Official TypeScript/JavaScript SDK for Tenlyr — the Tenant Infrastructure Layer for Modern SaaS.
One SDK call. Full tenant onboarded in < 200 ms.
What Tenlyr solves
Building multi-tenant SaaS means re-implementing the same infrastructure every time: provisioning, usage metering, isolation tiers, health monitoring, billing sync. Tenlyr handles all of it so you can focus on your product.
Installation
npm install @tenlyr/sdk30-second quickstart
import { Tenlyr } from '@tenlyr/sdk';
const tenlyr = new Tenlyr({ apiKey: 'tenlyr_live_…' });
// Provision a new tenant
const { tenant, apiKey } = await tenlyr.tenants.create({
name: 'Acme Corp',
ownerEmail: 'ceo@acme.com',
plan: 'growth',
region: 'us-east-1',
});
// Track usage
await tenlyr.usage.track({
tenantKey: apiKey,
metric: 'api_call',
value: 1,
});Example SaaS flow
import { Tenlyr, TenantPlan, UsageMetric } from '@tenlyr/sdk';
const tenlyr = new Tenlyr({ apiKey: 'tenlyr_live_…' });
// 1. User signs up — provision their tenant
const { tenant, apiKey } = await tenlyr.tenants.create({
name: 'Acme Corp',
ownerEmail: 'ceo@acme.com',
plan: 'growth' satisfies TenantPlan,
region: 'us-east-1',
});
// 2. Store the tenant key in your database
await db.saveTenant({ id: tenant.id, apiKey });
// 3. Track usage on every API call
const metric: UsageMetric = 'api_call';
await tenlyr.usage.track({ tenantKey: apiKey, metric, value: 1 });
// 4. Check fleet health
const health = await tenlyr.tenants.health();
const atRisk = health.filter(t => t.slaRisk);
// 5. Upgrade a tenant's isolation tier
const migration = await tenlyr.isolation.migrate(tenant.id, {
targetLevel: 'dedicated_schema',
});
await tenlyr.isolation.waitForMigration(migration.id);TypeScript types
The SDK is fully typed. Key types you'll use:
import type {
Tenant, // Core tenant record
TenantPlan, // 'starter' | 'growth' | 'scale' | 'enterprise'
TenantStatus, // 'active' | 'suspended' | 'pending' | 'terminated'
IsolationLevel, // 'shared_schema' | 'dedicated_schema' | 'dedicated_database'
UsageMetric, // 'api_call' | 'storage_gb' | 'seats'
HealthStatus, // 'healthy' | 'warning' | 'critical' | 'unknown'
AlertSeverity, // 'low' | 'medium' | 'high' | 'critical'
ProvisionOptions, // Input to tenants.create()
ProvisionResult, // { tenant: Tenant; apiKey: string }
TenlyrConfig, // Constructor options
} from '@tenlyr/sdk';Configuration
const tenlyr = new Tenlyr({
apiKey: 'tenlyr_live_…', // required — admin key
baseUrl: 'https://…', // default: https://www.tenlyr.com
timeout: 10_000, // ms, default 10 000
retries: 3, // 5xx retry count, default 3
retryDelay: 200, // initial back-off ms (exponential), default 200
});API reference
tenlyr.tenants
| Method | Description |
|---|---|
create(options) |
Provision a new tenant — returns { tenant, apiKey } |
list() |
List all tenants (admin) |
get(id) |
Get tenant by ID |
health() |
All tenants with merged health, usage, and billing |
update(id, fields) |
Update name / region |
suspend(id, reason?) |
Suspend (preserves data) |
reactivate(id) |
Reactivate suspended tenant |
terminate(id) |
Permanently terminate |
rotateApiKey(id) |
Rotate API key |
changePlan(id, plan) |
Change billing plan |
delete(id) |
Hard-delete tenant |
const { tenant, apiKey } = await tenlyr.tenants.create({
name: 'Acme Corp',
ownerEmail: 'ceo@acme.com',
plan: 'growth', // starter | growth | scale | enterprise
region: 'us-east-1',
});tenlyr.usage
| Method | Description |
|---|---|
track(opts) |
Track a usage event |
metrics(tenantKey) |
Aggregated counters |
summary(tenantKey) |
Current-month summary + recent events |
events(tenantKey, limit?) |
Raw event log |
await tenlyr.usage.track({
tenantKey: apiKey,
metric: 'api_call', // api_call | storage_gb | seats
value: 1,
endpoint: '/api/data', // optional
});tenlyr.health
| Method | Description |
|---|---|
overview() |
Counts by status + avg error rate |
list() |
All tenant health records |
get(tenantId) |
Single tenant health |
update(tenantId, metrics) |
Push error/request rate |
throttle(tenantId, limitRpm) |
Cap noisy tenant |
removeThrottle(tenantId) |
Remove rate cap |
tenlyr.isolation
| Method | Description |
|---|---|
migrate(tenantId, opts) |
Start isolation migration |
getMigration(id) |
Get migration by ID |
migrations(tenantKey) |
Migration history |
waitForMigration(id, opts?) |
Poll until complete/failed |
Isolation levels: shared_schema → dedicated_schema → dedicated_database
tenlyr.billing
| Method | Description |
|---|---|
get(tenantKey) |
Billing record |
portalUrl(tenantKey) |
Stripe customer portal URL |
tenlyr.alerts
| Method | Description |
|---|---|
list(key?) |
All alerts (admin = all tenants) |
unresolved(key?) |
Unresolved only |
create(opts, tenantKey) |
Create alert |
resolve(alertId) |
Mark resolved |
resolveAll(key?) |
Bulk resolve |
Error handling
The SDK throws specific error classes so you can handle each failure mode precisely:
import {
Tenlyr,
TenlyrError,
AuthenticationError, // 401 — bad or missing API key
PermissionError, // 403 — key lacks permission
NotFoundError, // 404 — tenant or resource not found
InvalidRequestError, // 422 — invalid request body
RateLimitError, // 429 — rate limit exceeded
TenlyrNetworkError, // network / connectivity failure
TenlyrTimeoutError, // per-request timeout exceeded
} from '@tenlyr/sdk';
try {
await tenlyr.tenants.create({ name: 'Acme', ownerEmail: 'ceo@acme.com', plan: 'growth', region: 'us-east-1' });
} catch (err) {
if (err instanceof AuthenticationError) {
// Invalid or revoked API key — check your tenlyr_live_… key
console.error('Auth failed:', err.message);
} else if (err instanceof RateLimitError) {
// Optionally respect the server-suggested back-off
const wait = err.retryAfter ?? 60;
console.warn(`Rate limited — retry in ${wait}s`);
} else if (err instanceof NotFoundError) {
console.error('Resource not found:', err.message);
} else if (err instanceof InvalidRequestError) {
console.error('Bad request:', err.body);
} else if (err instanceof TenlyrTimeoutError) {
console.error('Request timed out');
} else if (err instanceof TenlyrNetworkError) {
console.error('Network failure:', err.message);
} else if (err instanceof TenlyrError) {
// Catch-all for any other HTTP error
console.error(err.statusCode, err.message);
}
}All error classes extend TenlyrError, so instanceof TenlyrError works as a safe catch-all when you don't need granular handling.
Benchmark results (mock HTTP, pure SDK overhead)
| Operation | ops/sec | avg (µs) | p95 (µs) |
|---|---|---|---|
billing.get() |
318,227 | 3 | 5 |
alerts.unresolved() |
316,546 | 3 | 5 |
usage.track() |
311,163 | 3 | 4 |
health.throttle() |
152,947 | 6 | 4 |
tenants.get() |
142,201 | 7 | 6 |
health.overview() |
137,854 | 7 | 6 |
usage.metrics() |
135,495 | 7 | 6 |
isolation.migrate() |
115,515 | 9 | 5 |
tenants.create() |
85,104 | 11 | 9 |
tenants.list() |
55,264 | 18 | 6 |
tenants.suspend() |
38,800 | 26 | 6 |
All operations exceed 10,000 ops/sec. SDK overhead is negligible — network RTT dominates in production.
License
MIT