Skip to content
Settings

models — entity model schema system

cyoda-go version 0.6.2

models — entity model schema system: registration, lifecycle, import, export, and validation.

GET /api/model/
GET /api/model/export/{converter}/{entityName}/{modelVersion}
POST /api/model/import/{dataFormat}/{converter}/{entityName}/{modelVersion}
POST /api/model/validate/{entityName}/{modelVersion}
DELETE /api/model/{entityName}/{modelVersion}
POST /api/model/{entityName}/{modelVersion}/changeLevel/{changeLevel}
PUT /api/model/{entityName}/{modelVersion}/lock
PUT /api/model/{entityName}/{modelVersion}/unlock
GET /api/model/{entityName}/{modelVersion}/workflow/export
POST /api/model/{entityName}/{modelVersion}/workflow/import

Context path prefix is CYODA_CONTEXT_PATH (default /api).

A model is a named, versioned schema registered per tenant. Every entity in the system is an instance of exactly one model. Models are identified by (entityName, modelVersion). The model ID is a deterministic UUID v5 derived from that key: UUID.newSHA1(NameSpaceURL, "{entityName}.{modelVersion}").

Models have two lifecycle states: UNLOCKED and LOCKED. A LOCKED model blocks further imports. An UNLOCKED model accepts re-import and schema merging. Entities can only be created against a LOCKED model. Deletion is blocked while any entities reference the model.

Schema inference is additive: importing sample data against an existing model merges the incoming schema with the stored one. The model’s changeLevel field controls which structural changes are allowed during entity ingestion on a locked model.

GET /api/model/

List all models for the authenticated tenant.

Response: 200 OK, application/json, array of EntityModelDto.

POST /api/model/import/{dataFormat}/{converter}/{entityName}/{modelVersion}

Import or update a model schema from sample data. Body size limit: 10 MiB.

  • dataFormat (path): JSON or XML
  • converter (path): SAMPLE_DATA (only supported converter; JSON_SCHEMA and SIMPLE_VIEW are defined in the OpenAPI but return 400 BAD_REQUEST in this implementation)
  • entityName (path): string — model name
  • modelVersion (path): int32 — model version number

If the model does not exist, it is created with state UNLOCKED. If it exists and is UNLOCKED, the incoming schema is merged (additive). If it exists and is LOCKED, returns 409 CONFLICT.

Response: 200 OK, application/json, UUID string — the model ID.

GET /api/model/export/{converter}/{entityName}/{modelVersion}

Export a model schema in the specified format.

  • converter (path): JSON_SCHEMA or SIMPLE_VIEW
  • entityName (path): string
  • modelVersion (path): int32

Response: 200 OK, application/json — format depends on converter.

POST /api/model/validate/{entityName}/{modelVersion}

Validate a JSON payload against the model’s schema. Returns a result object, not an HTTP error, on validation failure.

  • entityName (path): string
  • modelVersion (path): int32

Request body: any JSON object.

Response: 200 OK, application/json, EntityModelActionResultDto.

DELETE /api/model/{entityName}/{modelVersion}

Delete a model. Blocked if the model is LOCKED or if any entities reference it (entity count > 0).

Response: 200 OK, application/json, EntityModelActionResultDto on success. 409 CONFLICT if entities exist.

POST /api/model/{entityName}/{modelVersion}/changeLevel/{changeLevel}

Set or update the change level on a model. Meaningful for locked models; unlocked models always allow all changes.

  • changeLevel (path): ARRAY_LENGTH, ARRAY_ELEMENTS, TYPE, or STRUCTURAL

Change levels are hierarchical (most restrictive to most permissive):

  • ARRAY_LENGTH — permits only increases in uni-type array width
  • ARRAY_ELEMENTS — allows multi-type array changes without adding new types
  • TYPE — permits modifications to existing types
  • STRUCTURAL — allows fundamental model changes including new fields

Response: 200 OK, application/json, EntityModelActionResultDto.

PUT /api/model/{entityName}/{modelVersion}/lock

Lock a model. The model must be UNLOCKED. Returns 409 CONFLICT if already locked.

Response: 200 OK, application/json, EntityModelActionResultDto.

PUT /api/model/{entityName}/{modelVersion}/unlock

Unlock a model. The model must be LOCKED and have zero associated entities. Returns 409 CONFLICT if entities exist or model is not locked.

Response: 200 OK, application/json, EntityModelActionResultDto.

GET /api/model/{entityName}/{modelVersion}/workflow/export

Export all workflow configurations for the model. Returns 404 WORKFLOW_NOT_FOUND if no workflows exist.

Response: 200 OK, application/json:

{
"entityName": "nobel-prize",
"modelVersion": 1,
"workflows": []
}

POST /api/model/{entityName}/{modelVersion}/workflow/import

Import or replace workflow configurations for the model. See workflows topic.

EntityModelDto (returned by GET /api/model/):

{
"id": "31134900-d9cb-11ee-b913-ae468cd3ed16",
"modelName": "nobel-prize",
"modelVersion": 1,
"currentState": "LOCKED",
"modelUpdateDate": "2025-08-02T13:31:48.141053-07:00"
}
  • id — UUID (deterministic v5, derived from name+version)
  • modelName — string
  • modelVersion — int32
  • currentState"LOCKED" or "UNLOCKED"
  • modelUpdateDate — RFC 3339 timestamp, nullable

EntityModelActionResultDto (returned by lock, unlock, delete, changeLevel, validate):

{
"success": true,
"message": "Model nobel-prize:1 locked",
"modelId": "cee334fa-c0ac-11f0-ba79-ae468cd3ed16",
"modelKey": {
"name": "nobel-prize",
"version": 1
}
}
  • success — boolean
  • message — human-readable result string
  • modelId — UUID
  • modelKey.name — string
  • modelKey.version — int32

Import request body (sample data, JSON format):

{
"category": "physics",
"year": "2024",
"laureates": [
{
"firstname": "John",
"surname": "Hopfield",
"id": "1037",
"motivation": "for foundational discoveries",
"share": "2"
}
]
}

The importer walks the JSON structure and infers a typed schema. Subsequent imports are merged additively.

Export — SIMPLE_VIEW format:

{
"currentState": "LOCKED",
"model": {
"$": {
"#.laureates": "OBJECT",
".category": "STRING",
".year": "STRING"
},
"$.laureates[*]": {
"#": "ARRAY_ELEMENT",
".firstname": "STRING",
".id": "STRING",
".motivation": "STRING",
".share": "STRING",
".surname": "STRING"
}
}
}

The "$" bucket includes a "#.fieldname": "OBJECT" entry for each array field in the root object. The "$.fieldname[*]" bucket contains the array element schema with "#": "ARRAY_ELEMENT" as a type marker.

Export — JSON_SCHEMA format:

{
"currentState": "LOCKED",
"model": {
"type": "object",
"properties": {
"category": { "type": "string" },
"year": { "type": "string" },
"laureates": {
"type": "array",
"items": {
"type": "object",
"properties": {
"firstname": { "type": "string" },
"share": { "type": "string" },
"id": { "type": "string" },
"surname": { "type": "string" },
"motivation": { "type": "string" }
}
}
}
}
}
}

A model moves between two states:

  • UNLOCKED — initial state after first import; re-import is permitted; entities cannot be created
  • LOCKED — entities can be created; re-import is blocked; change-level controls in-flight schema extension

Transitions:

  • UNLOCKEDLOCKED via PUT /model/{name}/{version}/lock
  • LOCKEDUNLOCKED via PUT /model/{name}/{version}/unlock (only when entity count is zero)

The changeLevel field controls schema evolution on locked models. When set, entity ingestion that introduces new structure triggers an additive schema extension (delta computed via schema.Diff, appended via ModelStore.ExtendSchema, committed with the entity transaction).

  • errors.MODEL_NOT_FOUND404 — model does not exist for the given name and version
  • errors.CONFLICT409 — import blocked (model is locked), lock failed (already locked), unlock failed (entities exist or model not locked), delete failed (entities exist)
  • errors.VALIDATION_FAILED400 — workflow import validation failed (static analysis)
  • errors.BAD_REQUEST400 — unsupported converter, invalid changeLevel, malformed body

Import a model from sample JSON:

curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"category":"physics","year":"2024","laureates":[{"firstname":"John","surname":"Hopfield","id":"1037"}]}' \
"http://localhost:8080/api/model/import/JSON/SAMPLE_DATA/nobel-prize/1"

Response: "1d1e1b10-1155-11f0-bcd5-ae468cd3ed16"

Lock the model:

curl -s -X PUT \
-H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/api/model/nobel-prize/1/lock"

Set change level (allow structural evolution on locked model):

curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/api/model/nobel-prize/1/changeLevel/STRUCTURAL"

List all models:

curl -s -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/api/model/"

Export as SIMPLE_VIEW:

curl -s -H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/api/model/export/SIMPLE_VIEW/nobel-prize/1"

Validate a payload against the model:

curl -s -X POST \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"category":"physics","year":"2024"}' \
"http://localhost:8080/api/model/validate/nobel-prize/1"

Delete a model (must be unlocked and have zero entities):

curl -s -X DELETE \
-H "Authorization: Bearer $TOKEN" \
"http://localhost:8080/api/model/nobel-prize/1"
  • crud
  • workflows
  • search
  • errors.MODEL_NOT_FOUND
  • errors.VALIDATION_FAILED
  • errors.CONFLICT
  • openapi
  • cyoda help crud — Entities are instances of models. Each entity has a UUID, a model reference (entityName, modelVersion), and a lifecycle state managed by the workflow engine. Creating an entity requires the referenced model to be in LOCKED state. All write operations run within a Cyoda transaction and return a transactionId alongside the affected entity IDs.
  • cyoda help workflows — A workflow definition is a named finite state machine attached to an entity model. Workflows are stored per model reference (entityName, modelVersion). A model may have multiple workflow definitions; the engine selects the matching one per entity using the workflow-level criterion field evaluated at entity creation time. When no criterion matches, the engine uses the default built-in workflow.
  • cyoda help search — Search operates against a specific entity model (entityName, modelVersion). Two modes are supported:
  • cyoda help errors MODEL_NOT_FOUND — The entity type or model name specified in the request does not exist in the tenant’s model registry. Occurs when creating entities with an unknown type, importing data that references a missing model, or performing model lifecycle transitions on a model ID that does not exist.
  • cyoda help errors VALIDATION_FAILED — Unlike BAD_REQUEST (which covers parse failures), this error is returned when the payload is parseable but violates the registered model schema — for example, a required field is missing, a value is out of the allowed range, or a workflow guard condition is not satisfied. The error detail includes the specific validation failure.
  • cyoda help errors CONFLICT — The server detected that the entity was modified by another writer between the time it was read and the time the current write was committed. Normal outcome under concurrent load.
  • cyoda help openapi — cyoda-go generates its OpenAPI 3.1 specification from the embedded api/openapi.yaml file compiled into the binary at build time. The spec is served at /openapi.json with runtime-patched server URLs. The Scalar API Reference UI is served at /docs and loads the spec from /openapi.json.