S
supero.docs
Developers/API Reference/Domains, Projects & Tenants API

Domains, Projects & Tenants API

Reference for the account hierarchy Supero is built on -- Domain, Project, and Tenant -- and the REST endpoints for registering a domain and managing tenant lifecycle.

Overview

Every account on Supero is organized into a Domain, containing one or more Projects, each containing one or more Tenants. Tenants are the data-isolation boundary: users, API keys, and CRUD records all live inside a specific tenant. This page is for anyone provisioning organizations programmatically -- SaaS builders onboarding a new customer, or an admin console calling the platform API directly.
A quick example: creating a new tenant inside an existing project.
bash
curl -X POST https://api.supero.dev/api/v1/tenants \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "acme-support",
    "display_name": "Acme Support Team",
    "project_uuid": "6f2b1a10-...-9e21"
  }'
The response contains the created tenant object with its uuid, which you use in every subsequent tenant-scoped call.

The Domain, Project & Tenant hierarchy

Supero resources form a 4-level hierarchy: Domain, then Project, then Tenant, then UserAccount or ApiKey. Each level narrows scope. Projects & Tenants walks through the same hierarchy from the dashboard side.
LevelWhat it isCreated via
DomainThe organization / account. Owns billing, has one or more Projects, and a top-level admin.POST /api/v1/domains/register (self-serve signup)
ProjectA workspace inside a Domain. Schemas are defined at the project level and are shared by every tenant in it.Raw CRUD: POST /api/v1/crud/{domain}/project
TenantAn isolation node inside a Project. Users and data records belong to exactly one tenant; queries never cross tenant boundaries.POST /api/v1/tenants, or raw CRUD: POST /api/v1/crud/{domain}/tenant
UserAccount / ApiKeyA login or API key scoped to a specific tenant (or, for admin roles, to a project or domain).Created under a tenant via the user/API-key management endpoints
When you register a new Domain, the platform automatically provisions a default-project and a default-tenant for it. Cross-tenant reach comes from the domain- and project-level roles — domain_admin, domain_user, project_admin and project_user see and manage every tenant in their scope, while tenant_admin, tenant_user, viewer and developer see only their own tenant. There is one exception that is not a role: anyone whose tenant is named default-tenant gets project-wide reach regardless of role, which is why customers should never be provisioned there. See Building Multi-Tenant Apps.

ℹ️ Namespace is not a hierarchy level

Namespace is a separate, orthogonal label used to group schemas (e.g. for versioning or multi-app separation within a project). It does not nest inside the Domain/Project/Tenant hierarchy and does not gate data access the way a tenant boundary does.

Authentication and roles

Authenticate with either a user session (Authorization: Bearer <jwt>, from POST /api/v1/auth/login) or a project/tenant API key (X-API-Key: ak_...), both covered in Authentication. Scope is derived from the token or key itself — there are no separate scope headers to set.
Domain and Tenant lifecycle operations are privileged: they are gated by role, not just by authentication. The closed set of roles is platform_admin, platform_user, domain_admin, domain_user, project_admin, project_user, developer, tenant_admin, tenant_user, viewer. platform_admin/platform_user are Supero-staff-only and are never assigned to customer accounts. Within a customer account, permissions break down as:
RoleCreate / delete tenantsUpdate any tenantUpdate own tenantRead tenants
domain_adminYes, anywhere in the domainYes--All tenants in the domain
project_adminYes, within their own projectYes, within their own project--All tenants in their project
domain_user / project_userNoNoNoAll tenants in domain/project (read-only)
developerNoNoNoAll tenants in their project (read-only)
tenant_adminNoNoYesTheir own tenant only
tenant_user / viewerNoNoNoTheir own tenant only (read-only)

⚠️ Heads up

Creating, suspending, unsuspending, archiving, or deleting a tenant requires domain_admin or project_admin (project-scoped). A tenant_admin can only read and update their own tenant's metadata -- they cannot manage other tenants or change lifecycle state.

Registering a Domain

To create a brand-new account (Domain), call the public registration endpoint. This provisions the domain, a default-project, a default-tenant, and an admin user in one call -- it does not require an existing session.
bash
curl -X POST https://api.supero.dev/api/v1/domains/register \
  -H "Content-Type: application/json" \
  -d '{
    "domain_name": "acme-corp",
    "domain_display_name": "Acme Corporation",
    "admin_email": "[email protected]",
    "admin_password": "S3cur3Pass!",
    "admin_first_name": "Jordan",
    "admin_last_name": "Lee"
  }'
json
{
  "success": true,
  "message": "Domain created successfully!",
  "tenant": {
    "domain_name": "acme-corp",
    "domain_uuid": "b1f0...",
    "default_project_uuid": "6f2b...",
    "default_tenant_uuid": "9c31...",
    "admin_user_uuid": "a44e...",
    "tenant_api_key": "ak_...",
    "db_type": "mongodb"
  },
  "auth": {
    "access_token": "...",
    "refresh_token": "...",
    "expires_in": 28800
  }
}
  • •domain_name must be 3-63 characters: lowercase letters, digits, and hyphens only.
  • •db_type is optional and defaults to mongodb; postgresql is also supported and is set once at registration time.
  • •A handful of reserved names (matching internal/infrastructure prefixes) are rejected -- pick a name specific to your organization.
  • •admin_email / admin_password create the domain_admin user for this new domain.

ℹ️ Existing sessions

If you call this endpoint while already authenticated, the platform associates the new domain with your account instead of creating a new admin user from admin_email/admin_password.

Creating Projects and Tenants via raw CRUD

Projects and tenants are both regular CRUD objects, so you can create them with the generic CRUD endpoint alongside your own schema types. This is the same pattern used for any domain/type combination: POST https://api.supero.dev/api/v1/crud/{domain}/{type}.

Create a project

bash
curl -X POST https://api.supero.dev/api/v1/crud/acme-corp/project \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "ecommerce",
    "fq_name": ["acme-corp", "ecommerce"],
    "parent_type": "domain",
    "description": "E-commerce platform"
  }'

Create a tenant inside that project

bash
curl -X POST https://api.supero.dev/api/v1/crud/acme-corp/tenant \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "acme-support",
    "fq_name": ["acme-corp", "ecommerce", "acme-support"],
    "parent_type": "project",
    "parent_uuid": "<project_uuid>",
    "display_name": "Acme Support Team",
    "description": "Support team workspace"
  }'
Records key on uuid, never on a client-supplied id. Read them back with GET /api/v1/crud/{domain}/project/{uuid} or GET /api/v1/crud/{domain}/tenant/{uuid}, and list them with GET /api/v1/crud/{domain}/tenant (optionally filtered).

💡 Which path to use

Use raw CRUD when you are scripting tenant/project creation alongside your own schema types with a single consistent client. Use the dedicated /api/v1/tenants endpoints (next section) when you need lifecycle operations -- activation, suspension -- or want to bootstrap a tenant admin user in the same call.

Tenant Management API

Alongside raw CRUD, Supero exposes dedicated tenant endpoints with business logic beyond a plain record write -- most notably, creating a tenant together with its admin user in one atomic call.
EndpointMethodDescription
/api/v1/tenantsPOSTCreate a tenant, optionally with an admin user
/api/v1/tenantsGETList tenants (optionally filtered by project_uuid, status)
/api/v1/tenants/{uuid}GETGet tenant details
/api/v1/tenants/{uuid}PUT / PATCHUpdate tenant fields
/api/v1/tenants/{uuid}DELETEDelete a tenant

Create a tenant with an admin user

bash
curl -X POST https://api.supero.dev/api/v1/tenants \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "acme-support",
    "display_name": "Acme Support Team",
    "project_uuid": "6f2b1a10-...-9e21",
    "create_admin_user": true,
    "admin_user": {
      "full_name": "Jordan Lee",
      "email": "[email protected]",
      "send_activation_email": true
    }
  }'
json
{
  "tenant": {
    "uuid": "9c31...",
    "name": "acme-support",
    "display_name": "Acme Support Team",
    "status": "active",
    "activation_status": "pending",
    "parent_type": "project",
    "parent_uuid": "6f2b1a10-...-9e21",
    "created_at": "2026-07-18T09:00:00Z"
  },
  "admin_user": {
    "uuid": "a44e...",
    "email": "[email protected]",
    "role": "tenant_admin",
    "tenant_uuid": "9c31...",
    "enabled": true,
    "email_verified": false,
    "temporary_password": "..."
  },
  "activation_required": true,
  "message": "Tenant created successfully. Activation email sent to admin."
}
tenant names follow the same rule as domain names: lowercase letters, digits, and hyphens only. If you omit admin_user.password, the platform generates one and returns it once in temporary_password -- store it immediately, it is not shown again. Set admin_user.send_activation_email to control whether an activation email goes out.

Tenant fields

FieldNotes
uuidSystem-assigned identifier; use this in all subsequent calls, never name.
nameImmutable after creation -- lowercase, digits, hyphens only.
display_name / descriptionEditable via PUT/PATCH.
statusactive | suspended | archived.
activation_statusactive | pending -- tracks whether the bootstrap admin user has completed activation.
parent_type / parent_uuidAlways parent_type: "project", pointing at the owning project.
fq_nameFully-qualified name path, e.g. ["acme-corp", "ecommerce", "acme-support"].
StatusMeaning
400Missing/invalid fields, e.g. a malformed tenant name.
403Caller's role cannot perform this operation, or is scoped to a different tenant.
404Tenant not found.
409A tenant with that name already exists in this domain.

Tenant lifecycle operations

These endpoints change a tenant's status and are restricted to domain_admin / project_admin roles (see the roles table above). They operate on an existing tenant by uuid.
EndpointMethodEffect
/api/v1/tenants/{uuid}/activatePOSTMarks the tenant active. Accepts an optional activation_token.
/api/v1/tenants/{uuid}/suspendPOSTMarks the tenant suspended; accepts an optional reason.
/api/v1/tenants/{uuid}/unsuspendPOSTReturns a suspended tenant to active.
/api/v1/tenants/{uuid}/archivePOSTMarks the tenant archived (soft delete).
/api/v1/tenants/{uuid}/resend-activationPOSTRe-sends the activation email to the tenant's admin user.
bash
curl -X POST https://api.supero.dev/api/v1/tenants/9c31.../suspend \
  -H "Authorization: Bearer <jwt>" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Payment overdue" }'

🚨 Important

The default-tenant of a project cannot be suspended, archived, or deleted -- it is a protected system tenant, not an ordinary customer tenant. Attempting to do so returns a 400.

Python SDK

The Python SDK covers domain registration, login, and generic CRUD (which works for tenant and project records the same way it works for your own schema types). Install it with pip install supero.
python
from supero import register_domain, login, connect

# First time only: create the domain, default project/tenant, and admin user
org = register_domain(
    domain_name="acme-corp",
    admin_email="[email protected]",
    admin_password="S3cur3Pass!",
)

# On subsequent runs, log in instead
org = login(domain_name="acme-corp", email="[email protected]", password="S3cur3Pass!")

# Or connect with a project/tenant API key
org = connect("acme-corp", api_key="ak_...")

# Create a tenant (raw CRUD path -- synchronous, returns the created record)
tenant = org.crud.create(
    "tenant",
    name="acme-support",
    display_name="Acme Support Team",
    parent_type="project",
    parent_uuid=project_uuid,
)

tenants = org.crud.list("tenant")
one = org.crud.get("tenant", tenant["uuid"])
org.crud.update("tenant", tenant["uuid"], display_name="Acme Support (EU)")
org.crud.delete("tenant", tenant["uuid"])
org.crud is synchronous. Lifecycle operations (activate, suspend, unsuspend, archive, resend-activation) are business-logic endpoints, not generic CRUD, so call them directly over REST as shown in the previous section.

Next steps

  • •Authentication -- login, API keys, and token refresh in detail.
  • •CRUD Endpoints -- the full create/read/update/delete/query contract shared by every object type, including tenant and project.
  • •Roles & Permissions Reference -- the complete permission set behind each of the ten roles.
  • •Schema Reference -- how to define the schemas your tenants' data will use.