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.
| Level | What it is | Created via |
|---|---|---|
| Domain | The organization / account. Owns billing, has one or more Projects, and a top-level admin. | POST /api/v1/domains/register (self-serve signup) |
| Project | A 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 |
| Tenant | An 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 / ApiKey | A 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:
| Role | Create / delete tenants | Update any tenant | Update own tenant | Read tenants |
|---|---|---|---|---|
| domain_admin | Yes, anywhere in the domain | Yes | -- | All tenants in the domain |
| project_admin | Yes, within their own project | Yes, within their own project | -- | All tenants in their project |
| domain_user / project_user | No | No | No | All tenants in domain/project (read-only) |
| developer | No | No | No | All tenants in their project (read-only) |
| tenant_admin | No | No | Yes | Their own tenant only |
| tenant_user / viewer | No | No | No | Their 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.
| Endpoint | Method | Description |
|---|---|---|
| /api/v1/tenants | POST | Create a tenant, optionally with an admin user |
| /api/v1/tenants | GET | List tenants (optionally filtered by project_uuid, status) |
| /api/v1/tenants/{uuid} | GET | Get tenant details |
| /api/v1/tenants/{uuid} | PUT / PATCH | Update tenant fields |
| /api/v1/tenants/{uuid} | DELETE | Delete 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
| Field | Notes |
|---|---|
| uuid | System-assigned identifier; use this in all subsequent calls, never name. |
| name | Immutable after creation -- lowercase, digits, hyphens only. |
| display_name / description | Editable via PUT/PATCH. |
| status | active | suspended | archived. |
| activation_status | active | pending -- tracks whether the bootstrap admin user has completed activation. |
| parent_type / parent_uuid | Always parent_type: "project", pointing at the owning project. |
| fq_name | Fully-qualified name path, e.g. ["acme-corp", "ecommerce", "acme-support"]. |
| Status | Meaning |
|---|---|
| 400 | Missing/invalid fields, e.g. a malformed tenant name. |
| 403 | Caller's role cannot perform this operation, or is scoped to a different tenant. |
| 404 | Tenant not found. |
| 409 | A 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.
| Endpoint | Method | Effect |
|---|---|---|
| /api/v1/tenants/{uuid}/activate | POST | Marks the tenant active. Accepts an optional activation_token. |
| /api/v1/tenants/{uuid}/suspend | POST | Marks the tenant suspended; accepts an optional reason. |
| /api/v1/tenants/{uuid}/unsuspend | POST | Returns a suspended tenant to active. |
| /api/v1/tenants/{uuid}/archive | POST | Marks the tenant archived (soft delete). |
| /api/v1/tenants/{uuid}/resend-activation | POST | Re-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.
On this page