Tenant Management
Provision and manage tenants for your SaaS packages. Each tenant represents a customer organization subscribing to your product.
Provision Tenant
POST /v1/saas-tenants Provision a new tenant for a SaaS package. Creates the tenant’s organization, configures their subscription, and applies the default feature flags for their plan.
Request Body
{
"saas_package_id": "...",
"tenant_name": "Acme Construction",
"tenant_slug": "acme-construction",
"admin_user_id": "...",
"admin_email": "admin@example.com",
"admin_display_name": "Acme Admin",
"tenant_settings": {
"industry": "construction",
"company_size": "50-200"
}
}
The owning organization is taken from the authenticated session. System-assigned
fields (id, tenant_org_id, status,
created_at, updated_at) are never supplied in the request
body; they are assigned by the platform.
Response
Returns the full created provision record on success.
{
"success": true,
"data": {
"id": "...",
"org_id": "...",
"saas_package_id": "...",
"tenant_org_id": "...",
"status": "completed",
"provisioned_resources": [
{ "type": "organization", "id": "...", "name": "Acme Construction" }
],
"started_at": "2026-04-13T10:00:00Z",
"completed_at": "2026-04-13T10:00:02Z",
"created_by": "...",
"created_at": "2026-04-13T10:00:00Z",
"updated_at": "2026-04-13T10:00:02Z"
}
} Response Codes
| Status | Meaning |
|---|---|
201 Created | Tenant provisioned; the full provision record is returned |
400 Bad Request | Validation error: missing/invalid field, malformed body, or the referenced package is not a published package owned by your organization |
401 Unauthorized | Authentication required or invalid |
403 Forbidden | Caller lacks the tenant-provision permission or active membership |
409 Conflict | A provision already exists for this subscriber and package, or the tenant_slug is already in use |
500 Internal Server Error | Unexpected server error |
List Tenants
GET /v1/saas-tenants Lists tenant provisions for the authenticated user’s organization (the owning org is resolved from the session).
Query Parameters
| Parameter | Type | Description |
|---|---|---|
saas_package_id | UUID | Filter by SaaS package (optional) |
status | string | Filter: pending, provisioning, completed, failed |
limit | integer | Max results (max: 100) |
offset | integer | Pagination offset |
Response
{
"data": [
{
"id": "...",
"org_id": "...",
"saas_package_id": "...",
"tenant_org_id": "...",
"status": "completed",
"created_at": "2026-03-15T10:00:00Z",
"completed_at": "2026-03-15T10:00:02Z"
}
],
"pagination": {
"total": 48,
"limit": 50,
"offset": 0,
"has_more": false
}
} Tenant Subscriptions
Create Tenant Subscription
POST /v1/saas-tenants/:packageId/subscriptions Request Body
{
"pricing_plan_id": "...",
"subscriber_org_id": "...",
"stripe_subscription_id": "sub_...",
"quantity": 5
}
The owning organization is taken from the authenticated session. System-assigned
fields (id, status, created_by,
created_at, updated_at) are never supplied in the request
body; they are assigned by the platform. The default status is
active.
Response
Returns the full created subscription record on success.
{
"success": true,
"data": {
"id": "...",
"org_id": "...",
"saas_package_id": "...",
"pricing_plan_id": "...",
"subscriber_org_id": "...",
"stripe_subscription_id": "sub_...",
"status": "active",
"current_period_start": null,
"current_period_end": null,
"trial_end": null,
"quantity": 5,
"prerequisite_subscription_id": null,
"cancelled_at": null,
"created_by": "...",
"created_at": "2026-04-13T10:00:00Z",
"updated_at": "2026-04-13T10:00:00Z"
}
} Response Codes
| Status | Meaning |
|---|---|
201 Created | Subscription created; the full record is returned |
400 Bad Request | Validation error: missing/invalid field, self-subscription attempt, or the referenced package or plan does not exist in your organization |
401 Unauthorized | Authentication required or invalid |
403 Forbidden | Caller lacks the subscription-create permission or active membership |
409 Conflict | An active subscription already exists for this subscriber and package |
500 Internal Server Error | Unexpected server error |
List Tenant Subscriptions
GET /v1/saas-tenants/:packageId/subscriptions Query Parameters
| Parameter | Type | Description |
|---|---|---|
subscriber_org_id | UUID | Filter by subscriber organization |
status | string | Filter by subscription status |
limit | integer | Max results (max: 100) |
offset | integer | Pagination offset |
Get Tenant Subscription
GET /v1/saas-tenants/:packageId/subscriptions/:subscriptionId Update Tenant Subscription
PUT /v1/saas-tenants/:packageId/subscriptions/:subscriptionId Request Body
{
"pricing_plan_id": "...",
"quantity": 10
} Cancel Tenant Subscription
POST /v1/saas-tenants/:packageId/subscriptions/:subscriptionId/cancel Cancels the specified customer subscription. The subscription is identified by the path parameters; no request body is required.
Feature Flags
Feature flags are defined per SaaS package and are nested under the package routes. Flag definitions are created on the package, then enabled or disabled at the package, pricing-plan, or per-tenant scope via separate assignment endpoints.
List Feature Flags
GET /v1/saas-packages/:packageId/feature-flags Create Feature Flag
POST /v1/saas-packages/:packageId/feature-flags Request Body
{
"slug": "advanced-analytics",
"name": "Advanced Analytics",
"description": "Enable advanced analytics dashboards",
"is_enabled": false
}
The owning organization is taken from the authenticated session. System-assigned
fields (id, created_by, created_at,
updated_at) are never supplied in the request body; they are
assigned by the platform. The slug must be lowercase letters, numbers,
and hyphens, and must be unique within the package.
Response
Returns the full created flag definition on success.
{
"success": true,
"data": {
"id": "...",
"org_id": "...",
"saas_package_id": "...",
"slug": "advanced-analytics",
"name": "Advanced Analytics",
"description": "Enable advanced analytics dashboards",
"is_enabled": false,
"metadata": {},
"created_by": "...",
"updated_by": "...",
"created_at": "2026-04-13T10:00:00Z",
"updated_at": "2026-04-13T10:00:00Z",
"archived_at": null,
"archived_by": null
}
} Response Codes
| Status | Meaning |
|---|---|
201 Created | Flag definition created; the full record is returned |
400 Bad Request | Validation error: missing required field or invalid slug / name |
401 Unauthorized | Authentication required or invalid |
403 Forbidden | Caller lacks the feature-flag-create permission or active membership |
404 Not Found | The SaaS package was not found in your organization |
409 Conflict | A feature flag with this slug already exists for the package |
500 Internal Server Error | Unexpected server error |
Get Feature Flag
GET /v1/saas-packages/:packageId/feature-flags/:flagId Update Feature Flag
PUT /v1/saas-packages/:packageId/feature-flags/:flagId Update the flag definition (slug, name, description, or default state).
Request Body
{
"name": "Advanced Analytics",
"is_enabled": true
} Delete Feature Flag
DELETE /v1/saas-packages/:packageId/feature-flags/:flagId Plan-Level Flag Assignments
GET /v1/saas-packages/:packageId/pricing-plans/:planId/feature-flags
PUT /v1/saas-packages/:packageId/pricing-plans/:planId/feature-flags Read or set which flags are enabled for a given pricing plan. The PUT body is a list of assignments by flag ID.
Request Body (PUT)
{
"assignments": [
{ "feature_flag_id": "...", "is_enabled": true }
]
} Tenant-Level Flag Assignments
GET /v1/saas-packages/:packageId/tenants/:tenantOrgId/feature-flags
PUT /v1/saas-packages/:packageId/tenants/:tenantOrgId/feature-flags
GET /v1/saas-packages/:packageId/tenants/:tenantOrgId/feature-flags/effective Read or override flags for a specific tenant organization, and read the effective (resolved) flag set. Tenant-level overrides take precedence over plan-level and package defaults.