Skip to content

Platform-hosted caching (Upstash)

An opt-in, platform-provisioned Redis-compatible cache for your app -- a dedicated Upstash database per (org, app, optional tenant), provisioned and billed by TenancyEngine so you never hold an Upstash account or vendor credential of your own. This follows the same credential-ownership pattern as the platform's SMS gateway and AI provider pass-through: the platform owns the vendor relationship, your app opts in and pays cost-plus-markup.

This is provisioning and billing, never a traffic proxy. Once you opt in, your app's own Redis client talks to Upstash directly over the credentials TenancyEngine hands you -- TenancyEngine is not in the hot path of your cache reads/writes.

Opting in

All endpoints are on the runtime surface (/api/v1/runtime/applications/{applicationId}/cache), authenticated with your organization API key.

http
POST /api/v1/runtime/applications/{applicationId}/cache?tenantId={optional}
X-TenancyEngine-Api-Key: te_...

Requires the cache.manage scope (deliberately excluded from every composite scope -- opting in provisions a real, billable Upstash database). Idempotent: calling it again for an already-active database returns the existing one rather than provisioning a second. Returns 503 upstash_not_configured if the platform's own Upstash account isn't configured for this environment, or 502 if the live provisioning call to Upstash fails.

http
DELETE /api/v1/runtime/applications/{applicationId}/cache?tenantId={optional}

Requires cache.manage. Deletes the live Upstash database -- everything cached in it is gone.

Getting connection details

http
GET /api/v1/runtime/applications/{applicationId}/cache/connection?tenantId={optional}
X-TenancyEngine-Api-Key: te_...

Requires cache.read. Returns the real host/port/token your own Redis client needs:

json
{
  "restUrl": "https://....upstash.io",
  "restToken": "...",
  "redisProtocolHost": "....upstash.io",
  "redisProtocolPort": 6379
}

Unlike an API key, this is not reveal-once -- your app legitimately needs to fetch it repeatedly (reconnect after a redeploy, refresh local config), so call this endpoint again rather than trying to cache the credential yourself long-term. It is still a real, live, full-access credential to your cache -- treat it with the same care as a database connection string.

Pricing model: cost-plus-markup

Same shape as AI credits -- raw vendor cost plus a configurable markup (floored at 1.0x), computed in TenancyPlatform.Infrastructure.Caching.CachePricing against Upstash's published pay-as-you-go rates:

MeterVendor ratePlatform unit
Commands executed~$0.20 / 100,000 commands (single-region)2 micro-USD/command
Storage held~$0.25/GB-month, billed continuously (not just on writes)~342 micro-USD/GB-hour

Storage is billed continuously because Upstash bills it that way -- data you leave cached but never touch still accrues a real, ongoing "idle-carry" cost, the same way it would on your own Upstash account.

http
GET /api/v1/runtime/applications/{applicationId}/cache/usage?tenantId={optional}
X-TenancyEngine-Api-Key: te_...

Requires cache.read. Returns your current balance and the last 50 metered-usage ledger entries (shared cache_redis-kind credit account, same ledger machinery as AI credits):

json
{
  "balanceMicroUsd": 1250000,
  "balanceUsd": 1.25,
  "lifetimeSpentMicroUsd": 380000,
  "recentUsage": [ { "entryType": "...", "amountMicroUsd": -420, "reason": "...", "createdAtUtc": "..." } ]
}
  • SDK reference -- the .NET SDK's dependency footprint note explains why caching support ships via Microsoft.Extensions.Caching.Memory/StackExchange.Redis for the in-memory and platform-Redis tiers, while the Upstash tier talks to Upstash's REST API directly over plain HttpClient rather than adding a Redis-protocol client dependency
  • AI credits -- the platform's other pay-as-you-go, cost-plus-markup add-on
  • API keys -- scopes and key creation

TenancyEngine platform documentation