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

StatusMeaning
201 CreatedTenant provisioned; the full provision record is returned
400 Bad RequestValidation error: missing/invalid field, malformed body, or the referenced package is not a published package owned by your organization
401 UnauthorizedAuthentication required or invalid
403 ForbiddenCaller lacks the tenant-provision permission or active membership
409 ConflictA provision already exists for this subscriber and package, or the tenant_slug is already in use
500 Internal Server ErrorUnexpected 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

ParameterTypeDescription
saas_package_idUUIDFilter by SaaS package (optional)
statusstringFilter: pending, provisioning, completed, failed
limitintegerMax results (max: 100)
offsetintegerPagination 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

StatusMeaning
201 CreatedSubscription created; the full record is returned
400 Bad RequestValidation error: missing/invalid field, self-subscription attempt, or the referenced package or plan does not exist in your organization
401 UnauthorizedAuthentication required or invalid
403 ForbiddenCaller lacks the subscription-create permission or active membership
409 ConflictAn active subscription already exists for this subscriber and package
500 Internal Server ErrorUnexpected server error

List Tenant Subscriptions

GET /v1/saas-tenants/:packageId/subscriptions

Query Parameters

ParameterTypeDescription
subscriber_org_idUUIDFilter by subscriber organization
statusstringFilter by subscription status
limitintegerMax results (max: 100)
offsetintegerPagination 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

StatusMeaning
201 CreatedFlag definition created; the full record is returned
400 Bad RequestValidation error: missing required field or invalid slug / name
401 UnauthorizedAuthentication required or invalid
403 ForbiddenCaller lacks the feature-flag-create permission or active membership
404 Not FoundThe SaaS package was not found in your organization
409 ConflictA feature flag with this slug already exists for the package
500 Internal Server ErrorUnexpected 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.