S
supero.docs
Developers/Platform Overview/Platform Concepts

Platform Concepts

The core mental model behind Supero: how domains, projects, tenants, and namespaces isolate your data, how schemas drive the API, and how roles and policies control access.

The Domain -> Project -> Tenant Hierarchy

Every object you create in Supero lives inside a strict hierarchy. Understanding it explains how isolation, access control, and API URLs all work. Core Concepts introduces the same ideas without the API detail.
LevelWhat it isChildren
DomainYour organization. The top-level container for everything you own.Projects (plus domain-level objects like schema registries and audit logs)
ProjectAn isolated backend application inside a domain, with its own schemas and API keys.Tenants
TenantA data partition inside a project. Single-tenant apps still get a default tenant.User accounts, API keys
Every object has a fully-qualified name (fq_name) that reflects this chain, e.g. ["acme", "storefront", "northwind"], plus a stable uuid. Records are always addressed by uuid, never by an "id" field.

ℹ️ Where scope comes from

A request's domain/project/tenant scope is resolved from the caller's JWT or API key, not from request headers you set yourself. You can narrow a read with query parameters such as project or tenant where an endpoint supports it, but you cannot use a header to escalate into a different project or tenant.

Namespaces: an Orthogonal Isolation Axis

Namespace is a second, independent isolation concept layered on top of the domain/project/tenant hierarchy. Every schema you upload declares a namespace, and object types are addressed as namespace:type (for example storefront:product). Two projects -- or two apps inside the same project -- can each define a type called order without colliding, because they sit in different namespaces.
Namespaces exist to let independently-built schemas coexist safely: your app schemas, an imported connector's schemas, and a marketplace service's schemas can all define similarly-named entities without one silently overwriting another. Declaring a namespace on every schema you upload is the safe habit, but it is not required: a schema that omits one inherits the namespace of the project you upload it into, falling back to tenant if that project has none set. Because the inherited value depends on the target project, the same schema file can land in different namespaces in different projects -- set it explicitly whenever that matters. See Schema Reference.
http
POST https://api.supero.dev/api/v1/schemas/upload
Authorization: Bearer <token>
Content-Type: application/json

{
  "domain_name": "acme",
  "project_uuid": "<project-uuid>",
  "schemas": [
    {
      "name": "Product",
      "schema_type": "object",
      "schema_category": "tenant",
      "schema_content": {
        "name": "Product",
        "namespace": "storefront",
        "parent_type": "tenant",
        "attributes": [
          { "name": "title", "type": "string", "mandatory": true },
          { "name": "price", "type": "float", "mandatory": true },
          { "name": "order_state", "type": "string", "values": ["draft", "active", "archived"] }
        ],
        "references": [
          { "name": "Category", "cardinality": "one", "back_ref_name": "products" }
        ]
      },
      "version": "1.0.0"
    }
  ]
}

💡 Resolving names to UUIDs

Because most object types are namespace-qualified, use POST /api/v1/crud/{domain}/fq-name-to-uuid to resolve a fully-qualified name (or an array of path segments) to a uuid instead of hardcoding one.

Schemas: the Source of Truth

A schema is the single definition that drives everything else on the platform: the REST endpoints under /api/v1/crud/{domain}/{type}, the generated SDK surface, the admin UI, and validation. You define schemas as small JSON (or Python dict) documents and upload them with POST /api/v1/schemas/upload.

Schema anatomy

  • •name -- the type name, unique within its namespace.
  • •namespace -- the isolation group this schema belongs to (see above).
  • •parent_type -- what this object is a child of: project, tenant, or another schema type. Determines the fq_name depth and default access scoping.
  • •fields[] -- fields, each with name, type, and optionally required, values (for an inline enum), and a default value.
  • •references[] -- named relationships to other types, with a cardinality (one/many) and an optional back_ref_name for the reverse lookup.
  • •extends -- optional, in the form "<service>:<base_type>", used to build a type on top of a platform base type (for example a payment record that extends payment:base_payment).

Field types

A field's type is either one of the platform primitives, or the name of a type or enum schema you defined yourself (optionally namespace-qualified). Many common synonyms are accepted and canonicalized on upload. Schema Reference carries the full table of primitive types, their aliases, and the built-in system types -- treat it as the single source for this list.
Every stored object also carries a fixed set of system fields regardless of your schema: uuid, created_at, updated_at, fq_name, parent_type, and parent_uuid. You never define these yourself.

ℹ️ AI-assisted schema design

You do not have to hand-write every schema. The AI assistant in App Studio can propose a schema from a natural-language description of your data model, which you then review, edit, and upload.

Projects, Tenants, and CRUD

A project is created under a domain and, once you have a project, most day-to-day work happens through the CRUD API scoped to it. Projects are created through the same CRUD surface as your data, using the dedicated project type.
http
POST https://api.supero.dev/api/v1/crud/acme/project
Authorization: Bearer <token>

{
  "name": "storefront",
  "fq_name": ["acme", "storefront"],
  "parent_uuid": "<domain-uuid>",
  "parent_type": "domain",
  "create_api_key": true,
  "api_key_scopes": ["read", "write", "delete"]
}
Creating a project can optionally mint an API key in the same call. That key is shown once -- store it securely; it is what your backend services use for X-API-Key authentication going forward.
Within a project, tenants isolate data for multi-tenant apps -- one tenant per customer, per department, or per environment. Single-tenant apps still get a default tenant automatically, so you can ignore tenants entirely until you need multi-tenancy.

Working with objects

  • •POST /api/v1/crud/{domain}/{type} -- create an object; requires name and, for non-root objects, parent_uuid and parent_type.
  • •GET /api/v1/crud/{domain}/{type}/{uuid} -- fetch one object.
  • •GET /api/v1/crud/{domain}/{type} -- list objects (non-admin callers are automatically scoped to their project).
  • •PUT /api/v1/crud/{domain}/{type}/{uuid} -- update fields (fq_name itself cannot be changed).
  • •DELETE /api/v1/crud/{domain}/{type}/{uuid} -- delete an object.
  • •POST /api/v1/crud/{domain}/query -- richer reads with filters and field selection.
  • •POST /api/v1/crud/{domain}/list-bulk -- fetch many objects efficiently in one call.

💡 Singular type names

Object types in CRUD URLs are singular -- product, not products.

Roles and Access

Supero has one fixed, closed set of roles. There is no admin/editor/viewer naming -- roles map directly onto the domain/project/tenant hierarchy, plus a couple of cross-cutting roles.
RoleScope
domain_adminFull control within a domain, including creating projects.
domain_userDomain-scoped, read/limited-write access.
project_adminFull control within a project.
project_userProject-scoped, limited-write access.
developerCreate, read, and update within a project -- the typical integration/service-account role.
tenant_adminFull control within a tenant.
tenant_userThe typical end-customer role -- scoped to their own tenant.
viewerRead-only access.
platform_admin and platform_user also exist, but they are Supero-operated platform-level roles, not roles you assign to your own customers.
On login, the response includes a derived ui_view of either "admin" or "customer" -- "admin" for domain/project/tenant admins and developers, "customer" for everyone else (tenant_user, project_user, domain_user, viewer). Client apps use this to decide whether to boot the admin shell or the customer-facing storefront, instead of pattern-matching on role strings themselves.
To see exactly what the current caller is allowed to do, call the access-policy endpoint rather than inferring it from the role name alone -- custom roles can be layered on top of the built-in ones and resolve to one of the roles above for permission purposes. Access Policies (Entity, Record & Field) covers what those policies can express.
http
GET https://api.supero.dev/api/v1/crud/acme/access-policy/me
Authorization: Bearer <token>

Authentication Basics

Two credential types work against the API: a user JWT (obtained via login, for interactive/dashboard-style access) and an API key (for services and backend integrations).
CredentialHeaderTypical use
JWT access tokenAuthorization: Bearer <token>Logged-in users, session-based access
API keyX-API-Key: ak_...Server-to-server integrations, long-lived automation
http
POST https://api.supero.dev/api/v1/auth/login
Content-Type: application/json

{
  "domain": "acme",
  "email": "[email protected]",
  "password": "..."
}

// Response (abridged)
{
  "auth": { "access_token": "...", "refresh_token": "...", "token_type": "Bearer", "expires_in": 28800, "session_id": "..." },
  "user": { "role": "tenant_admin", "ui_view": "admin", "...": "..." },
  "context": { "domain": "acme", "project": "storefront", "tenant": "northwind", "scope": { "level": "tenant" } },
  "policy": { "role": "tenant_admin", "entities": { "...": "..." } }
}
Access tokens are short-lived; refresh with POST /api/v1/auth/refresh and revoke a session with POST /api/v1/auth/logout. Never send an API key as a Bearer token -- the two schemes are not interchangeable. Authentication has the token lifetimes, the refresh flow, and how to mint and rotate API keys.

Workflows, Sagas, and Extensibility

Beyond plain CRUD, Supero has an event-driven workflow engine: you can bind a workflow to a schema event (such as an object entering a given state), and the platform runs it as a series of steps -- including multi-step sagas that coordinate several schema types together. This is what powers things like an order transitioning through payment, fulfillment, and notification steps without you hand-wiring each transition.
For extending the platform with outside functionality, the primary path is the managed services catalog -- browse it with GET /api/v1/services/catalog, bring a service into your project with POST /api/v1/services/import, and configure it with PUT /api/v1/services/{id}/config. If you need to wire up your own custom HTTP endpoint instead, the Plugin Registry (/api/v1/plugins/*) is the secondary path for that.

Next Steps