S
supero.docs
Developers/API Reference/Schema API

Schema API

Create, validate, read, update, and delete schema definitions over REST — the same operations the dashboard, CLI, and AI Assistant use under the hood.

Overview

Schemas are the foundation of every Supero project: they define the entities, fields, data types, and relationships that drive the generated CRUD API, SDK bindings, and admin UI. You normally author schemas through the dashboard, the CLI, or the AI assistant in App Studio during app generation — but the full schema lifecycle is also available over REST, which is useful for CI pipelines, custom tooling, and scripted migrations.
Schema management endpoints require a Bearer JWT (or an API key with the schema:manage / schema:read permission) and are scoped to your domain. Read endpoints need schema:read; upload, update, and delete need schema:manage.
EndpointMethodDescription
/api/v1/schemas/uploadPOSTCreate or update one or more schemas (single unified upload path)
/api/v1/schemas/{schema_uuid}GETGet full details for one schema
/api/v1/schemas/{schema_uuid}PUTUpdate an existing schema’s content
/api/v1/schemas/{schema_uuid}DELETEDelete a schema
/api/v1/schemas/{schema_uuid}/check-dependenciesGETCheck whether other schemas or live objects depend on this schema
/api/v1/domains/{domain_name}/schemasGETList all schemas in your domain
/api/v1/domains/{domain_name}/schemas/validatePOSTValidate one or more schema definitions without saving them
/api/v1/projects/{project_uuid}/schemasGETList the schemas linked to a specific project
/api/v1/projects/{project_uuid}/schemas/{namespace}GETList schemas in a project, filtered to one namespace
/api/v1/projects/{project_uuid}/schemas/{namespace}/{schema_name}POST, GET, PUT, DELETECreate, read, update, or delete a single namespace-scoped schema by name

ℹ️ Not read-only

The full schema lifecycle — create, validate, read, update, delete, and dependency checks — is available over REST. This page documents the real request and response shapes for every schema endpoint.

The schema content model

Every schema you upload has an outer envelope (name, schema_type, namespace, version) and a schema_content body that describes the fields. Object schemas use attributes[] and, optionally, references[] to other entities; enum schemas use a values list; type schemas are reusable field bundles referenced by object schemas.
FieldLocationDescription
nameschema envelopeProgrammatic schema name, used in CRUD URLs (e.g. Customer).
schema_typeschema envelopeOne of object, type, or enum.
schema_categoryschema envelopeTypically tenant for app-defined schemas.
namespaceschema envelopeOrthogonal grouping label for the schema (e.g. billing, catalog). Omit it to inherit the project’s schema_namespace, which falls back to "tenant".
schema_content.parent_typeschema_contentThe parent object type in the hierarchy this schema attaches under (e.g. project, tenant).
schema_content.extendsschema_contentOptional — "<namespace>:<base_schema_name>" to inherit fields and references from a base schema.
schema_content.attributes[]schema_contentField list: {name, type, mandatory, values, default-value}. See Schema Reference for the full list of primitive types and the aliases accepted for each; enum-like fields use type:string with values:[...].
schema_content.references[]schema_contentRelationships to other schema types.

ℹ️ attributes is canonical; fields is accepted

The samples on this page use the canonical spelling: attributes for the field list inside schema_content, and mandatory on an individual field. The older spellings still work — fields is accepted as a synonym for attributes, and required as a synonym for mandatory — but the two pairs do not behave the same once you read back. A body sent with fields is stored under attributes, so a schema you upload and then GET never returns the key you wrote, and a diff of request against response will always show a mismatch that is not a failure. required is accepted but never rewritten, so a schema authored with required reads back with required while anything it inherits through extends carries mandatory, giving you both spellings in one response. Write attributes and mandatory in anything you maintain.

ℹ️ System fields

Every record carries uuid, created_at, updated_at, fq_name, parent_type, and parent_uuid automatically — records key on uuid, not id, and you never declare these fields yourself.

Uploading schemas

POST /api/v1/schemas/upload is the single unified path for creating and updating schemas. Every upload must be linked to a real, user-created project — pass project_uuid, or project_name plus domain_name and the server resolves it for you. Uploads to the platform's default placeholder project are rejected.
bash
curl -X POST https://api.supero.dev/api/v1/schemas/upload \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "domain_name": "acme",
    "project_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "schemas": [
      {
        "name": "Customer",
        "schema_type": "object",
        "schema_category": "tenant",
        "namespace": "crm",
        "version": "1.0.0",
        "schema_content": {
          "name": "Customer",
          "parent_type": "project",
          "attributes": [
            { "name": "full_name", "type": "string", "mandatory": true },
            { "name": "email", "type": "string", "mandatory": true },
            {
              "name": "status",
              "type": "string",
              "values": ["active", "inactive", "pending"],
              "default-value": "pending"
            }
          ]
        }
      }
    ]
  }'

Request fields

FieldTypeDescription
domain_namestringRequired. Your domain.
project_uuid / project_namestringRequired (one of the two). Schemas must be linked to a real project.
schemasarrayRequired, non-empty. Each entry is a schema envelope as shown above.
skip_existingbooleanSkip schemas that already exist by name instead of erroring. Default true.
check_compatibilitybooleanRun breaking-change checks against the current version before applying. Default true.
fail_fastbooleanAbort the whole batch on the first failure instead of applying the rest. Default false.
force_uploadbooleanBypass certain non-critical checks. Default false.
dry_runbooleanReturn a reconciliation plan without writing anything.

Response

json
{
  "success": true,
  "schemas": [ { "uuid": "...", "name": "Customer", "namespace": "crm", "schema_type": "object", "...": "..." } ],
  "project_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "failed": [],
  "skipped": [],
  "validation_warnings": [],
  "summary": { "total": 1, "uploaded": 1, "updated": 0, "skipped": 0, "failed": 0 }
}

⚠️ HTTP status reflects partial failure

201 means every schema in the batch applied cleanly; 207 means some applied and some failed (check the failed[] array); 422 means nothing was applied because at least one schema was rejected (usually a breaking change); 200 means every schema was skipped as unchanged.

Validating schemas before upload

POST /api/v1/domains/{domain_name}/schemas/validate runs the same checks as upload — cross-schema type resolution, dependency ordering, and name-conflict detection — without writing anything. Use it in CI to catch schema errors before they reach an upload step.
bash
curl -X POST https://api.supero.dev/api/v1/domains/acme/schemas/validate \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "project_uuid": "550e8400-e29b-41d4-a716-446655440000",
    "schemas": [
      {
        "name": "Customer",
        "schema_type": "object",
        "schema_content": {
          "name": "Customer",
          "parent_type": "project",
          "attributes": [{ "name": "email", "type": "string", "mandatory": true }]
        }
      }
    ]
  }'
json
{
  "valid": true,
  "error": null,
  "dependency_order": ["Customer"],
  "type_counts": { "object": 1, "enum": 0, "type": 0 },
  "validation_errors": [],
  "conflicts": [],
  "cycles": []
}
A schema with no explicit namespace is validated as if it inherits the target project’s schema_namespace (falling back to "tenant"), matching exactly what upload will do — so a passing validate call is a reliable predictor of a successful upload.

Listing and reading schemas

There are two ways to list schemas depending on scope: everything in your domain, or everything linked to one project.

List all schemas in your domain

http
GET /api/v1/domains/acme/schemas?schema_type=object&include_content=true HTTP/1.1
Host: api.supero.dev
Authorization: Bearer <token>
Query params: schema_type (object/type/enum), schema_category, status, include_content (default true), and resolve — pass resolve=true to get the extends-merged shape with inherited fields and references flattened in.

List schemas linked to a project

http
GET /api/v1/projects/550e8400-e29b-41d4-a716-446655440000/schemas?include_system=true HTTP/1.1
Host: api.supero.dev
Authorization: Bearer <token>
Returns schemas linked via explicit references plus, when include_system=true (the default), the core system schemas (project, user_account, api_key, tenant) every project implicitly has. Each entry is tagged linked_via (refs, project_uuid, or system) and is_system.
json
{
  "success": true,
  "project_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "project_name": "Storefront",
  "domain_name": "acme",
  "schemas": [
    { "name": "Customer", "linked_via": "refs", "is_system": false, "...": "..." },
    { "name": "project", "linked_via": "system", "is_system": true, "...": "..." }
  ],
  "total": 8,
  "linked_count": 4,
  "unlinked_count": 0,
  "system_count": 4
}

Get one schema by UUID

http
GET /api/v1/schemas/{schema_uuid}?include_content=true HTTP/1.1
Host: api.supero.dev
Authorization: Bearer <token>
Returns the full schema_registry object for that UUID, including schema_content when include_content is true (the default).

Updating and deleting schemas

Update

PUT /api/v1/schemas/{schema_uuid} replaces the schema’s content. With check_compatibility (default true), the server blocks changes that would orphan real data — removing or repointing a reference that has linked records, changing parent_type while objects exist, or removing a field that has data in it. Fields proven to have no data on any existing object can still be dropped even when the schema is not empty.
bash
curl -X PUT https://api.supero.dev/api/v1/schemas/{schema_uuid} \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "new_schema_content": {
      "name": "Customer",
      "parent_type": "project",
      "attributes": [
        { "name": "full_name", "type": "string", "mandatory": true },
        { "name": "email", "type": "string", "mandatory": true },
        { "name": "phone", "type": "string", "mandatory": false }
      ]
    },
    "check_compatibility": true
  }'
json
{
  "success": true,
  "message": "Schema updated successfully",
  "summary": { "total_schemas": 1, "updated": 1 },
  "steps": [ { "schema_name": "Customer", "schema_type": "object", "action": "update", "schema_uuid": "...", "s3_key": "..." } ]
}
A blocked breaking change returns 422 with a breaking[] array describing exactly which reference or field is in the way.

Check dependencies before deleting

http
GET /api/v1/schemas/{schema_uuid}/check-dependencies HTTP/1.1
Host: api.supero.dev
Authorization: Bearer <token>
json
{
  "schema_uuid": "...",
  "schema_name": "Customer",
  "schema_type": "object",
  "has_dependencies": false,
  "dependent_objects": [],
  "object_count": 12,
  "can_delete": false,
  "delete_blocked_reason": "Has 12 existing objects"
}

Delete

http
DELETE /api/v1/schemas/{schema_uuid}?force_delete=false&delete_objects=false HTTP/1.1
Host: api.supero.dev
Authorization: Bearer <token>
By default a schema with existing objects or dependents is refused. Pass delete_objects=true to also delete the schema’s existing records, or force_delete=true to override the dependency guard — use both with care.

Namespace-scoped schema routes

For tooling that manages one schema at a time by name rather than by UUID, every project also exposes namespace-scoped CRUD routes:
EndpointMethodDescription
/api/v1/projects/{project_uuid}/schemas/{namespace}/{schema_name}POSTCreate a schema with this name in this namespace
/api/v1/projects/{project_uuid}/schemas/{namespace}/{schema_name}GETRead one schema by namespace + name
/api/v1/projects/{project_uuid}/schemas/{namespace}/{schema_name}PUTUpdate one schema by namespace + name
/api/v1/projects/{project_uuid}/schemas/{namespace}/{schema_name}DELETEDelete one schema by namespace + name
/api/v1/projects/{project_uuid}/schemas/{namespace}GETList every schema in one namespace for a project
Namespace values are validated on write: reserved prefixes (sys, sysadmin, supero, supero_core, config, and anything starting with an underscore or ns_) are rejected with 400, as are names with spaces, names that don’t start with a letter, or names over 63 characters.
To attach an already-uploaded schema to a second project, or detach one, use the CRUD link routes: POST /api/v1/crud/{domain_name}/projects/{project_uuid}/schemas with {schema_uuid, schema_fq_name} in the body, and DELETE /api/v1/crud/{domain_name}/projects/{project_uuid}/schemas/{schema_uuid} to unlink.

Public schema and UI access

Projects with public access enabled expose a small, unauthenticated set of read routes for public-facing sites and embeds, scoped to the entities you explicitly mark public.
EndpointMethodDescription
/api/v1/public/{domain}/{project_uuid}/configGETPublic app configuration (which entities and views are public)
/api/v1/public/{domain}/{project_uuid}/schema/{entity}GETThe public entity’s schema, with hidden fields stripped
/api/v1/public/{domain}/{project_uuid}/ui/fileGETThe project’s generated UI bundle, served as JavaScript
/api/v1/public/{domain}/{project_uuid}/{entity}GETList public records for one entity
/api/v1/public/{domain}/{project_uuid}/{entity}/{uuid}GETRead one public record

⚠️ No Authorization header on public routes

These routes require no token — access is gated entirely by the project’s own public_entities configuration. Never rely on obscurity of the project UUID; only mark an entity public if its data is meant to be world-readable.

Errors

Schema endpoints use standard HTTP status codes with a JSON error body. Errors & Status Codes collects the platform-wide catalog.
StatusMeaning
400Bad request — missing required field, invalid namespace, or an unresolvable schema reference.
401Missing or invalid token.
403The schema or project belongs to a different domain than your token.
404Schema or project not found.
409A schema with that name already exists and skip_existing is false.
422Upload or update rejected because it would break existing data (a removed/repointed reference or field with live data, or a changed parent_type).
500Unexpected server error — retry, and include the request in a support ticket if it persists.
json
{
  "error": "Not Found: Schema 550e8400-e29b-41d4-a716-446655440000 not found",
  "message": "Schema 550e8400-e29b-41d4-a716-446655440000 not found"
}

Next steps

  • •CRUD Endpoints — create, read, update, and delete records against the schemas you define here.
  • •Authentication — obtaining and refreshing the Bearer tokens and API keys used on every schema endpoint.
  • •Projects & Tenants — how projects, namespaces, and schema_namespace defaults fit together.
  • •CLI (App Runner) — pushing and pulling schema files from the command line during local development.