Skip to main content
CoreSDK
Multi-Tenancy

Quotas & Rate Limits

Per-tenant request rate limits, storage quotas, and plan-based enforcement in CoreSDK.

Phase note. Rate limiting and quota enforcement ships in Phase 2 (coresdk-ratelimit). The API shown is the planned surface.

Quotas & Rate Limits

Phase 2. Per-tenant quotas, monthly request caps, storage quotas, and the quota middleware ship Phase 2.

CoreSDK enforces quotas at the tenant layer — request rate limits, storage caps, and seat counts — with built-in middleware and a quota events API.

Per-tenant rate limits

Rate limits are applied per tenant before any request reaches your application logic. Exceeding the limit returns 429 Too Many Requests with a Retry-After header.

use coresdk_engine::{CoreSDK, quota::{RateLimit, Window}};

let sdk = Engine::from_env()?
    .rate_limit(RateLimit {
        default_rpm: 1_000,                 // global default
        window: Window::Sliding,
        per_tenant: hashmap! {
            "acme"      => 10_000,
            "beta-corp" => 5_000,
            "free-tier" => 100,
        },
    })
    .build()
    .await?;
sdk = SDK.from_env()  # Phase 2 planned API
    .rate_limit({
        "default_rpm": 1_000,
        "window": "sliding",
        "per_tenant": {
            "acme":      10_000,
            "beta-corp": 5_000,
            "free-tier": 100,
        },
    }) \
    .build()
sdk, err := coresdk.NewBuilder().
    RateLimit(coresdk.RateLimit{
        DefaultRPM: 1_000,
        Window:     coresdk.WindowSliding,
        PerTenant: map[string]int{
            "acme":      10_000,
            "beta-corp": 5_000,
            "free-tier": 100,
        },
    }).
    Build(ctx)
const sdk = new CoreSDK({ tenant: "acme-corp" })  // Phase 2 planned API
  .rateLimit({
    defaultRpm: 1_000,
    window: "sliding",
    perTenant: {
      acme:       10_000,
      "beta-corp": 5_000,
      "free-tier": 100,
    },
  })
  .build();

Request quotas (monthly caps)

Monthly request quotas cut off access once a tenant exhausts their allocation. CoreSDK resets counters at the start of each billing cycle and emits a quota.exhausted event when the cap is hit.

use coresdk_engine::quota::{RequestQuota, QuotaExhaustedAction};

let sdk = Engine::from_env()?
    .request_quota(RequestQuota {
        // Plan-based defaults; per-tenant overrides win
        free_monthly: 10_000,
        starter_monthly: 100_000,
        business_monthly: 2_000_000,
        enterprise_monthly: None,           // unlimited
        on_exhausted: QuotaExhaustedAction::Block,
        notify_at_pct: vec![80, 95, 100],   // emit events at these thresholds
    })
    .build()
    .await?;

// Override for a specific tenant
sdk.quotas().set("acme", QuotaOverride {
    monthly_requests: Some(5_000_000),
    ..Default::default()
}).await?;
sdk = SDK.from_env()  # Phase 2 planned API
    .request_quota({
        "free_monthly":       10_000,
        "starter_monthly":    100_000,
        "business_monthly":   2_000_000,
        "enterprise_monthly": None,         # unlimited
        "on_exhausted":       "block",
        "notify_at_pct":      [80, 95, 100],
    }) \
    .build()

# Override for a specific tenant
await sdk.quotas.set("acme", {"monthly_requests": 5_000_000})
sdk, _ := coresdk.NewBuilder().
    RequestQuota(coresdk.RequestQuota{
        FreeMonthly:       10_000,
        StarterMonthly:    100_000,
        BusinessMonthly:   2_000_000,
        EnterpriseMonthly: nil,             // unlimited
        OnExhausted:       coresdk.QuotaBlock,
        NotifyAtPct:       []int{80, 95, 100},
    }).
    Build(ctx)

// Override for a specific tenant
sdk.Quotas().Set(ctx, "acme", coresdk.QuotaOverride{
    MonthlyRequests: ptr(5_000_000),
})
const sdk = new CoreSDK({ tenant: "acme-corp" })  // Phase 2 planned API
  .requestQuota({
    freeMonthly:       10_000,
    starterMonthly:    100_000,
    businessMonthly:   2_000_000,
    enterpriseMonthly: null,                // unlimited
    onExhausted:       "block",
    notifyAtPct:       [80, 95, 100],
  })
  .build();

// Override for a specific tenant
await sdk.quotas.set("acme", { monthlyRequests: 5_000_000 });

Storage quotas

CoreSDK tracks bytes written per tenant across blob storage, audit logs, and cached state. Writes that exceed the cap return 507 Insufficient Storage.

use coresdk_engine::quota::StorageQuota;

let sdk = Engine::from_env()?
    .storage_quota(StorageQuota {
        free_gb:       1.0,
        starter_gb:    10.0,
        business_gb:   100.0,
        enterprise_gb: None,    // unlimited
    })
    .build()
    .await?;

// Check current usage
let usage = sdk.quotas().storage_usage("acme").await?;
println!("{:.2} GB used of {:.2} GB", usage.used_gb, usage.limit_gb);
sdk = SDK.from_env()  # Phase 2 planned API
    .storage_quota({
        "free_gb":       1.0,
        "starter_gb":    10.0,
        "business_gb":   100.0,
        "enterprise_gb": None,  # unlimited
    }) \
    .build()

usage = await sdk.quotas.storage_usage("acme")
print(f"{usage.used_gb:.2f} GB used of {usage.limit_gb:.2f} GB")
sdk, _ := coresdk.NewBuilder().
    StorageQuota(coresdk.StorageQuota{
        FreeGB:       1.0,
        StarterGB:    10.0,
        BusinessGB:   100.0,
        EnterpriseGB: nil,  // unlimited
    }).
    Build(ctx)

usage, _ := sdk.Quotas().StorageUsage(ctx, "acme")
fmt.Printf("%.2f GB used of %.2f GB\n", usage.UsedGB, usage.LimitGB)
const sdk = new CoreSDK({ tenant: "acme-corp" })  // Phase 2 planned API
  .storageQuota({
    freeGb:       1.0,
    starterGb:    10.0,
    businessGb:   100.0,
    enterpriseGb: null,   // unlimited
  })
  .build();

const usage = await sdk.quotas.storageUsage("acme");
console.log(`${usage.usedGb.toFixed(2)} GB used of ${usage.limitGb.toFixed(2)} GB`);

Plan-based limits

Resolve the effective limit for a tenant at runtime to drive upgrade prompts or pre-flight checks.

let limits = sdk.quotas().effective_limits("acme").await?;

// limits.requests_per_minute — resolved from plan + any overrides
// limits.monthly_requests
// limits.storage_gb
// limits.seats

if limits.storage_gb.map(|l| usage_gb > l).unwrap_or(false) {
    return Err(AppError::StorageCapReached);
}
limits = await sdk.quotas.effective_limits("acme")
# limits.requests_per_minute, .monthly_requests, .storage_gb, .seats

if limits.storage_gb and usage_gb > limits.storage_gb:
    raise StorageCapReachedError()
limits, _ := sdk.Quotas().EffectiveLimits(ctx, "acme")

if limits.StorageGB != nil && usageGB > *limits.StorageGB {
    return ErrStorageCapReached
}
const limits = await sdk.quotas.effectiveLimits("acme");

if (limits.storageGb !== null && usageGb > limits.storageGb) {
  throw new StorageCapReachedError();
}

Enforcing limits in middleware

Mount quota enforcement as middleware so limits are applied uniformly before handlers run.

use axum::{Router, middleware};
use coresdk_engine::middleware::QuotaLayer;

let app = Router::new()
    .route("/api/*path", get(handler).post(handler))
    .layer(QuotaLayer::new(sdk.clone())
        .enforce_rate_limit(true)
        .enforce_monthly_quota(true)
        .on_exceeded(|tenant, quota_type| async move {
            tracing::warn!(tenant, ?quota_type, "quota exceeded");
        })
    );
# FastAPI
from coresdk.middleware import QuotaMiddleware

app.add_middleware(
    QuotaMiddleware,
    sdk=sdk,
    enforce_rate_limit=True,
    enforce_monthly_quota=True,
    on_exceeded=lambda tenant, quota_type: logger.warning(
        "quota exceeded", tenant=tenant, quota_type=quota_type
    ),
)
// net/http
mux := http.NewServeMux()
mux.HandleFunc("/api/", handler)

h := coresdk.QuotaMiddleware(sdk,
    coresdk.WithRateLimit(true),
    coresdk.WithMonthlyQuota(true),
    coresdk.OnExceeded(func(tenant, quotaType string) {
        slog.Warn("quota exceeded", "tenant", tenant, "quota_type", quotaType)
    }),
)(mux)
// Express
import { quotaMiddleware } from "@coresdk/express";

app.use(
  quotaMiddleware(sdk, {
    enforceRateLimit: true,
    enforceMonthlyQuota: true,
    onExceeded: (tenant, quotaType) => {
      logger.warn("quota exceeded", { tenant, quotaType });
    },
  })
);

Next steps

On this page