Entity model export
A model export returns the full structural model of an entity type in a compact JSON form. It carries every node, every field and every declared type.
Two converters render the model. SIMPLE_VIEW is Cyoda’s native
path-keyed form. JSON_SCHEMA is a JSON Schema document. This page is the
wire-format specification for both.
For the concepts behind a model, and for discovery, widening and locking, see Modeling entities and Entities and lifecycle.
Endpoint
Section titled “Endpoint”GET /model/export/{converter}/{entityName}/{modelVersion}| Parameter | Type | Description |
|---|---|---|
converter |
String | SIMPLE_VIEW or JSON_SCHEMA |
entityName |
String | Name of the entity model |
modelVersion |
Integer | Version number of the entity model |
Response Content-Type: application/json
Response envelope
Section titled “Response envelope”Both converters return the same envelope. currentState and model are always
present; uniqueKeys appears only when the model declares composite unique
keys.
| Key | Type | Description |
|---|---|---|
currentState |
String | Lifecycle state of the model: "UNLOCKED" or "LOCKED" |
model |
Object | The exported model, in the converter’s own form |
uniqueKeys |
Array | Composite unique-key definitions; omitted when none are declared |
{ "currentState": "LOCKED", "model": { "…": "…" }, "uniqueKeys": [ { "id": "by-email", "fields": ["$.email"] }, { "id": "by-org-and-handle", "fields": ["$.org", "$.handle"] } ]}SIMPLE_VIEW
Section titled “SIMPLE_VIEW”The model value is a flat JSON object. Its keys are node paths, and its
values are node descriptors. This single-depth map holds the full nested
tree. Each descriptor is an object, and it describes one structural level.
Node paths
Section titled “Node paths”A node path uses JSONPath syntax rooted at $. It is the same path that a
search condition uses to address the same place.
| Path | Meaning |
|---|---|
$ |
The root object |
$.address |
A nested object field |
$.laureates[*] |
The elements of the laureates array on the root object |
$.orders[*].lines[*] |
The elements of lines, nested inside orders array elements |
Write one [*] for each array level. The server sorts the node paths
lexicographically. Every export contains the root node $.
Node descriptors
Section titled “Node descriptors”A node descriptor is a JSON object with two kinds of entry.
Data fields — keys starting with . carry the field’s
type descriptor:
{ ".category": "STRING", ".year": "YEAR", ".score": "DOUBLE"}An array field’s key carries one [*] per array level, and the value is the
type of the element at that depth:
{ ".tags[*]": "STRING", ".matrix[*][*]": "INTEGER"}An array level whose elements were never observed declares the level, but not
the element type. Its value is "NULL":
{ ".e[*]": "NULL" }Structural fields — keys starting with # mark this node’s role. Only two
values are emitted:
| Key | Value | Meaning |
|---|---|---|
# |
"ARRAY_ELEMENT" |
This node describes the elements of its parent array |
#.fieldName |
"OBJECT" |
fieldName carries object structure and has its own node |
A nested object is not inlined into its parent with a dotted key. It gets a
#.fieldName marker in the parent and a node of its own:
{ "$": { "#.address": "OBJECT", ".name": "STRING" }, "$.address": { ".city": "STRING", ".zip": "STRING" }}The server sorts the keys inside a node. Structural keys (#) come before
data keys (.), because # precedes . in ASCII.
Kind unions
Section titled “Kind unions”A field observed in more than one kind declares each kind, and the model enforces each one. The export therefore names each branch.
A field seen as both a string and an object carries a data entry and a structural marker. The object branch gets a node of its own:
{ "$": { "#.o": "OBJECT", ".o": "STRING" }, "$.o": { ".a": "INTEGER" }}A field seen as both a scalar and an array names both forms in one node:
{ ".poly": "STRING", ".poly[*]": "STRING" }Type descriptors
Section titled “Type descriptors”A type descriptor is a JSON string: either one DataType name, or a
bracketed set when the field declares several.
Monomorphic:
"STRING"Polymorphic:
"[INTEGER, STRING]"Types inside the brackets are sorted by the DataType enum ordering: the
integer families, then the decimal families, then text, temporal, identifier,
binary, boolean, and NULL last.
DataType values
Section titled “DataType values”Twenty types, and no others:
| Group | DataType | Description |
|---|---|---|
| Integer | INTEGER |
32-bit signed integer |
LONG |
64-bit signed integer | |
BIG_INTEGER |
Arbitrary-precision integer fitting Int128 | |
UNBOUND_INTEGER |
Integer of arbitrary magnitude | |
| Decimal | DOUBLE |
Decimal within the precision-15, scale-292 envelope |
BIG_DECIMAL |
Decimal fitting Trino’s fixed-scale Int128 encoding | |
UNBOUND_DECIMAL |
Decimal of arbitrary precision and scale | |
| Text | STRING |
Variable-length character sequence |
CHARACTER |
A single Unicode character | |
| Temporal | LOCAL_DATE |
Date without time zone |
LOCAL_DATE_TIME |
Date-time without time zone | |
LOCAL_TIME |
Time without date | |
ZONED_DATE_TIME |
Date-time with time zone | |
YEAR |
Year value | |
YEAR_MONTH |
Year and month | |
| Identifier | UUID_TYPE |
UUID |
TIME_UUID_TYPE |
Version 1 (time-based) UUID | |
| Binary | BYTE_ARRAY |
Binary data, base64-encoded |
| Boolean | BOOLEAN |
Boolean value |
| Null | NULL |
No type observed at this leaf |
SIMPLE_VIEW examples
Section titled “SIMPLE_VIEW examples”Flat object with an array of objects. In this example "2020" classifies
as YEAR, not STRING. A date-shaped sample value gives the field a temporal
type at registration.
// posted: {"category":"chemistry","year":"2020",// "laureates":[{"firstname":"Emmanuelle","surname":"Charpentier","id":"991"}]}{ "currentState": "UNLOCKED", "model": { "$": { "#.laureates": "OBJECT", ".category": "STRING", ".year": "YEAR" }, "$.laureates[*]": { "#": "ARRAY_ELEMENT", ".firstname": "STRING", ".id": "STRING", ".surname": "STRING" } }}Nested object and a primitive array. The array’s element type is named once; its length is not part of the model.
// posted: {"name":"Alice","scores":[95,87,92],"address":{"city":"London","zip":"SW1A"}}{ "currentState": "UNLOCKED", "model": { "$": { "#.address": "OBJECT", ".name": "STRING", ".scores[*]": "INTEGER" }, "$.address": { ".city": "STRING", ".zip": "STRING" } }}Array of arrays. One [*] per level, in the parent node. There is no
detached node for the inner array.
// posted: {"matrix":[[1,2,3],[4,5,6]]}{ "currentState": "UNLOCKED", "model": { "$": { ".matrix[*][*]": "INTEGER" } }}Polymorphic scalar, after ingesting {"data":"hello"} and {"data":42}:
{ "currentState": "UNLOCKED", "model": { "$": { ".data": "[INTEGER, STRING]" } }}Heterogeneous array. Element types merge into one set, and a null
contributes no type:
// posted: {"row":[1,null,"three"]}{ "currentState": "UNLOCKED", "model": { "$": { ".row[*]": "[INTEGER, STRING]" } }}Kind union across a container and a scalar, after ingesting
{"data":[{"nested":"primitive"}]} and {"data":[[123,321],[456,654]]}:
{ "currentState": "UNLOCKED", "model": { "$": { "#.data": "OBJECT", ".data[*][*]": "INTEGER" }, "$.data[*]": { "#": "ARRAY_ELEMENT", ".nested": "STRING" } }}Two levels of object arrays:
// posted: {"orders":[{"lines":[{"sku":"AX-1","qty":2}]}]}{ "currentState": "UNLOCKED", "model": { "$": { "#.orders": "OBJECT" }, "$.orders[*]": { "#": "ARRAY_ELEMENT", "#.lines": "OBJECT" }, "$.orders[*].lines[*]": { "#": "ARRAY_ELEMENT", ".qty": "INTEGER", ".sku": "STRING" } }}JSON Schema for the SIMPLE_VIEW response
Section titled “JSON Schema for the SIMPLE_VIEW response”{ "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "SIMPLE_VIEW Model Export Response", "description": "Response from GET /model/export/SIMPLE_VIEW/{entityName}/{modelVersion}", "type": "object", "required": ["currentState", "model"], "additionalProperties": false, "properties": { "currentState": { "type": "string", "enum": ["LOCKED", "UNLOCKED"], "description": "Lifecycle state of the entity model." }, "model": { "type": "object", "description": "Map of node paths to node descriptors. Always contains at least the root node '$'.", "propertyNames": { "pattern": "^\\$(\\.[A-Za-z0-9_-]+(\\[\\*\\])*)*$" }, "additionalProperties": { "$ref": "#/$defs/nodeDescriptor" } }, "uniqueKeys": { "type": "array", "description": "Composite unique keys declared on the model. Omitted when none are declared.", "items": { "type": "object", "required": ["id", "fields"], "properties": { "id": { "type": "string" }, "fields": { "type": "array", "items": { "type": "string" }, "minItems": 1 } } } } }, "$defs": { "dataType": { "type": "string", "enum": [ "INTEGER", "LONG", "BIG_INTEGER", "UNBOUND_INTEGER", "DOUBLE", "BIG_DECIMAL", "UNBOUND_DECIMAL", "STRING", "CHARACTER", "LOCAL_DATE", "LOCAL_DATE_TIME", "LOCAL_TIME", "ZONED_DATE_TIME", "YEAR", "YEAR_MONTH", "UUID_TYPE", "TIME_UUID_TYPE", "BYTE_ARRAY", "BOOLEAN", "NULL" ] }, "typeDescriptor": { "description": "One DataType name, or a bracketed set when the field declares several.", "type": "string", "examples": ["STRING", "INTEGER", "NULL", "[INTEGER, STRING]"] }, "structuralDataType": { "type": "string", "description": "Value of a '#'-prefixed structural key.", "enum": ["OBJECT", "ARRAY_ELEMENT"] }, "nodeDescriptor": { "type": "object", "description": "Keys prefixed with '.' are data fields; keys prefixed with '#' are structural markers.", "propertyNames": { "pattern": "^((\\.[A-Za-z0-9_-]+(\\[\\*\\])*)|(#(\\.[A-Za-z0-9_-]+)?))$" }, "additionalProperties": { "anyOf": [ { "$ref": "#/$defs/typeDescriptor" }, { "$ref": "#/$defs/structuralDataType" } ] } } }}JSON_SCHEMA
Section titled “JSON_SCHEMA”The JSON_SCHEMA converter renders the same model as a JSON Schema document
under the same envelope. Objects become {"type": "object", "properties": …},
arrays become {"type": "array", "items": …}, and leaves map onto JSON Schema
primitives:
| DataType | Rendered as |
|---|---|
INTEGER, LONG, BIG_INTEGER, UNBOUND_INTEGER |
{"type": "integer"} |
DOUBLE, BIG_DECIMAL, UNBOUND_DECIMAL |
{"type": "number"} |
STRING, CHARACTER |
{"type": "string"} |
BOOLEAN |
{"type": "boolean"} |
NULL |
{"type": "null"} |
LOCAL_DATE |
{"type": "string", "format": "date"} |
LOCAL_DATE_TIME, ZONED_DATE_TIME |
{"type": "string", "format": "date-time"} |
LOCAL_TIME |
{"type": "string", "format": "time"} |
YEAR |
{"type": "string", "format": "year"} |
YEAR_MONTH |
{"type": "string", "format": "year-month"} |
UUID_TYPE, TIME_UUID_TYPE |
{"type": "string", "format": "uuid"} |
BYTE_ARRAY |
{"type": "string", "format": "byte"} |
A field that declares more than one branch renders as anyOf, never
oneOf. Two DataType values can render the same shape, because INTEGER and
LONG both give {"type": "integer"}, so a union may carry identical
branches.
// posted: {"data":"hello"} then {"data":42}{ "currentState": "UNLOCKED", "model": { "type": "object", "properties": { "data": { "anyOf": [{ "type": "integer" }, { "type": "string" }] } } }}The same rule covers a kind union. A field seen as both a scalar and an array
renders as an anyOf over the array branch and the scalar branch:
// posted: {"poly":"a"} then {"poly":["b","c"]}{ "currentState": "UNLOCKED", "model": { "type": "object", "properties": { "poly": { "anyOf": [ { "type": "array", "items": { "type": "string" } }, { "type": "string" } ] } } }}Error responses
Section titled “Error responses”| Status | Condition | Error code |
|---|---|---|
| 404 | Model not found | MODEL_NOT_FOUND |
| 400 | Unknown converter value | BAD_REQUEST |
404 example (captured):
{ "type": "about:blank", "title": "Not Found", "status": 404, "detail": "MODEL_NOT_FOUND: cannot find model entityName=nobel-prize, version=2", "instance": "/model/export/SIMPLE_VIEW/nobel-prize/2", "properties": { "entityName": "nobel-prize", "entityVersion": 2, "errorCode": "MODEL_NOT_FOUND" }}Key behaviours for consumers
Section titled “Key behaviours for consumers”-
Output is deterministic. Node paths are sorted lexicographically, and keys within a node are sorted, so two exports of the same model are byte-identical.
-
Models evolve by merging. When you ingest entities under a model whose change level permits it, new fields appear, the declared types of a leaf widen (
INTEGER→[INTEGER, STRING]), and a field can declare a new kind. The export does not report the length of an array. -
Every declared branch is exported. A field that declares more than one kind names each kind. The export describes what the model enforces.
-
An export is not an import format.
SAMPLE_DATAis the only converter that the import endpoint accepts. APOSTof an export to/model/import/JSON/SIMPLE_VIEW/…returns400 BAD_REQUEST: unsupported import converter. To establish a model, post representative sample documents.
Where to go next
Section titled “Where to go next”- Modeling entities — what a field declares, the change-level ladder, and the field-name grammar.
- Searching entities — the path grammar these node paths mirror.
- JSON schema reference — the REST-API message schemas generated from cyoda-go.