Developer quickstart (TenancyEngine)
Add TenancyEngine to an ISV backend in the same first-pass flow developers expect from Supabase, Firebase, or Vercel docs: create an app, mint a server-side key, install the SDK, read runtime context, record usage, and verify webhooks.
Runtime keys are server-side credentials. Do not ship an organization API key in browser JavaScript, mobile apps, or public source.
1. Create an application
- Open the TenancyEngine console.
- Go to Applications -> Create application.
- Copy the application slug, for example
vectralabel. - Open Environments and confirm the environment you will call, usually
Developmentlocally.
Local development uses:
- SaaSRuntime API:
https://auth.lab.saasruntime.com - Console:
https://console.lab.tenancyengine.com - Docs:
https://docs.lab.tenancyengine.com
Production apps should use the environment-specific authority/API base URL shown in the application runtime context.
2. Create an organization API key
Open API keys and create a key for your organization. Give the key only the scopes needed by your backend:
| Need | Scope |
|---|---|
| Read runtime context and application state | applications.read |
| Read tenant state and usage | tenants.read |
| Create tenants or consume/release usage | tenants.write |
| Send analytics events | analytics:write |
| Local MCP automation | mcp.full |
Store the key in your secret manager. TenancyEngine shows the plain value once.
3. Pick a key tier, then install an SDK
Organization API keys come in two binding tiers, plus a reserved admin tier no app key should ever hold:
| Tier | Scope | Use it for |
|---|---|---|
| App-scoped | Bound to exactly one application; every call is checked against that binding | A single app's own backend, smaller blast radius if the key leaks. Default choice. |
| Org-wide | Works across every application your organization owns | A backend that manages multiple TenancyEngine apps (e.g. an agency, or a platform team) |
Admin (admin/admin.*) | Full org scope; app-scoped keys can never hold it | Platform/org administration only, not a normal integration credential |
Full SDK list, install commands, and verified quickstarts for every language and platform (.NET, TypeScript, Go, Python, Java, PHP, Ruby, browser, and all five mobile auth SDKs): SDK reference. Short version for the two most complete SDKs:
TenancyEngine.Sdk (.NET) is published -- GitHub Packages feed nuget.pkg.github.com/sathish4000, versions 1.0.0/1.0.1. @tenancy-engine/sdk (TypeScript) is not yet published to npm (verified: 404 on the public registry) -- build it from source (cd packages/tenancy-engine-sdk && npm ci && npm run build) and consume it as a local/file: dependency until it ships. For any other language, use the package source under D:\repos\tenancy-platform\packages\ and ask the platform owner to publish it.
TypeScript
npm install @tenancy-engine/sdkimport { TenancyEngineClient } from '@tenancy-engine/sdk';
const te = new TenancyEngineClient({
baseUrl: process.env.TENANCYENGINE_BASE_URL ?? 'https://auth.lab.saasruntime.com',
apiKey: process.env.TENANCYENGINE_API_KEY!,
appId: process.env.TENANCYENGINE_APP_ID!,
});.NET
dotnet add package TenancyEngine.Sdkusing TenancyEngine.Sdk;
builder.Services.AddHttpClient("TenancyEngine", client =>
{
client.BaseAddress = new Uri(builder.Configuration["TenancyEngine:BaseUrl"]
?? "https://auth.lab.saasruntime.com");
});
builder.Services.AddScoped(sp =>
{
var httpFactory = sp.GetRequiredService<IHttpClientFactory>();
var apiKey = sp.GetRequiredService<IConfiguration>()["TenancyEngine:ApiKey"]
?? throw new InvalidOperationException("TenancyEngine:ApiKey is required.");
return new TenancyEngineClient(httpFactory.CreateClient("TenancyEngine"), apiKey);
});4. Gate a request with runtime context
Runtime context is the one backend call your app should make before tenant-scoped work. It returns enabled features, limits, current usage, tenant status, and OIDC settings for the selected environment.
const context = await te.getRuntimeContext('vectralabel', 'Development', tenantId);
if (!context.features.includes('labels.ai')) {
throw new Response('Feature not enabled', { status: 403 });
}
const limit = context.limits['labels.monthly'];
const used = context.usage['labels.monthly'] ?? 0;
if (limit !== null && limit !== undefined && used >= limit) {
throw new Response('Plan limit reached', { status: 402 });
}var context = await te.GetRuntimeContextAsync("vectralabel", "Development", tenantId, ct);
if (context is null || !context.Features.Contains("labels.ai"))
{
return Results.Forbid();
}
var limit = context.Limits.GetValueOrDefault("labels.monthly");
var used = context.Usage.GetValueOrDefault("labels.monthly");
if (limit is not null && used >= limit)
{
return Results.Problem("Plan limit reached", statusCode: StatusCodes.Status402PaymentRequired);
}5. Record usage with idempotency
Tie the idempotency key to your own operation ID so retries do not double-count usage.
const result = await te.consumeUsage(
'vectralabel',
'demo-bakery',
'Development',
'labels.monthly',
1,
`label:${labelId}`,
);var result = await te.ConsumeUsageAsync(
"vectralabel",
"demo-bakery",
"Development",
"labels.monthly",
amount: 1,
idempotencyKey: $"label:{labelId}",
ct);If the downstream operation fails after usage is consumed, call releaseUsage / ReleaseUsageAsync with a separate rollback idempotency key.
6. Verify webhooks
Create a webhook endpoint from Application -> Webhooks. Copy the signing secret when it is shown.
TenancyEngine sends X-TE-Signature: t={unixTimestamp},v1={hexHmac}. The HMAC input is {timestamp}.{rawBody}. Always verify the exact raw body bytes before parsing JSON.
import { verifyWebhookSignature } from '@tenancy-engine/sdk';
const rawBody = await request.text();
const signature = request.headers.get('X-TE-Signature') ?? '';
if (!verifyWebhookSignature(rawBody, signature, process.env.TENANCYENGINE_WEBHOOK_SECRET!)) {
throw new Response('Invalid signature', { status: 400 });
}var rawBody = await new StreamReader(Request.Body).ReadToEndAsync();
var signature = Request.Headers["X-TE-Signature"].ToString();
if (!WebhookSignatureVerifier.Verify(rawBody, signature, webhookSecret))
{
return Results.BadRequest("Invalid signature");
}7. Use MCP for setup automation
The TenancyEngine MCP server exposes console/admin/runtime tools for AI agents and local automation.
cd D:\repos\tenancy-platform\mcp-server
npm ci
npm run build
$env:TE_BASE_URL = "https://auth.lab.saasruntime.com"
$env:TE_API_KEY = "<organization-api-key>"
npm startUse TE_API_KEY for runtime tools such as get_runtime_context, consume_usage, and release_usage. Use an OIDC bearer token or local dev admin key for platform-admin tools.
Related
SDK reference - install commands and quickstarts for every language and platform
Mobile and browser apps - PKCE OIDC credential model for mobile/browser apps
Applications - register and configure your first app
API keys - create and revoke organization developer keys
Application webhooks - webhook signing and delivery logs
Application analytics - runtime analytics events
Tenant entitlements - feature and limit state
Setup wizard quick win - try a read-only API call from the console