﻿# Entity model export

Wire format of the SIMPLE_VIEW and JSON_SCHEMA entity-model exports — response envelope, node descriptors, type descriptors, and error shapes.

<VendoredBanner stability="evolving" />

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](/build/modeling-entities/) and
[Entities and lifecycle](/concepts/entities-and-lifecycle/).

## 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

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 |

```json
{
    "currentState": "LOCKED",
    "model": { "…": "…" },
    "uniqueKeys": [
        { "id": "by-email", "fields": ["$.email"] },
        { "id": "by-org-and-handle", "fields": ["$.org", "$.handle"] }
    ]
}
```

## 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

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

A node descriptor is a JSON object with two kinds of entry.

**Data fields — keys starting with `.`** carry the field's
[type descriptor](#type-descriptors):

```json
{
    ".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:

```json
{
    ".tags[*]": "STRING",
    ".matrix[*][*]": "INTEGER"
}
```

An array level whose elements were never observed declares the level, but not
the element type. Its value is `"NULL"`:

```json
{ ".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:

```json
{
    "$": { "#.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

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:

```json
{
    "$": { "#.o": "OBJECT", ".o": "STRING" },
    "$.o": { ".a": "INTEGER" }
}
```

A field seen as both a scalar and an array names both forms in one node:

```json
{ ".poly": "STRING", ".poly[*]": "STRING" }
```

### 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

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

**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.

```json
// 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.

```json
// 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.

```json
// posted: {"matrix":[[1,2,3],[4,5,6]]}
{
    "currentState": "UNLOCKED",
    "model": { "$": { ".matrix[*][*]": "INTEGER" } }
}
```

**Polymorphic scalar**, after ingesting `{"data":"hello"}` and `{"data":42}`:

```json
{
    "currentState": "UNLOCKED",
    "model": { "$": { ".data": "[INTEGER, STRING]" } }
}
```

**Heterogeneous array.** Element types merge into one set, and a `null`
contributes no type:

```json
// 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]]}`:

```json
{
    "currentState": "UNLOCKED",
    "model": {
        "$": {
            "#.data": "OBJECT",
            ".data[*][*]": "INTEGER"
        },
        "$.data[*]": {
            "#": "ARRAY_ELEMENT",
            ".nested": "STRING"
        }
    }
}
```

**Two levels of object arrays:**

```json
// 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

```json
{
    "$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

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.

```json
// 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:

```json
// posted: {"poly":"a"} then {"poly":["b","c"]}
{
    "currentState": "UNLOCKED",
    "model": {
        "type": "object",
        "properties": {
            "poly": {
                "anyOf": [
                    { "type": "array", "items": { "type": "string" } },
                    { "type": "string" }
                ]
            }
        }
    }
}
```

## Error responses

| Status | Condition               | Error code          |
|--------|-------------------------|---------------------|
| 404    | Model not found         | `MODEL_NOT_FOUND`   |
| 400    | Unknown converter value | `BAD_REQUEST`       |

**404 example** (captured):

```json
{
    "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

1. **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.

2. **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.

3. **Every declared branch is exported.** A field that declares more than one
   kind names each kind. The export describes what the model enforces.

4. **An export is not an import format.** `SAMPLE_DATA` is the only converter
   that the import endpoint accepts. A `POST` of an export to
   `/model/import/JSON/SIMPLE_VIEW/…` returns
   `400 BAD_REQUEST: unsupported import converter`. To establish a model, post
   representative sample documents.

## Where to go next

- [Modeling entities](/build/modeling-entities/) — what a field declares, the
  change-level ladder, and the field-name grammar.
- [Searching entities](/build/searching-entities/#how-a-field-path-is-written) —
  the path grammar these node paths mirror.
- [JSON schema reference](/reference/schemas/) — the REST-API message schemas
  generated from cyoda-go.