Compositions

A composition is the presentation layout used when an ontology's passports are rendered publicly — which tabs exist, which properties appear where, the masthead, and how it all looks. Each ontology has at most one composition, and it shares the ontology's id. The layout is written in the ontology's default language, with every other language kept beside it as translations. Ontologies without one still render, from a synthesized default layout, so a composition is only needed when you want control over how a passport is presented — not its schema.

The composition model

  • Name
    object
    Type
    string
    Description

    Always composition.

  • Name
    id
    Type
    string
    Description

    Unique identifier. A composition is 1:1 with its ontology and shares its id, e.g. ontology_7d1f4a2b9c3e5081.

  • Name
    ontology
    Type
    string
    Description

    The id of the ontology this composition belongs to. Always equal to id.

  • Name
    name
    Type
    string
    Description

    Display name for the layout, copied from the ontology's name when the composition is created. Nullable.

  • Name
    description
    Type
    string
    Description

    Short description of the layout, copied from the ontology's description when the composition is created. Nullable.

  • Name
    default_language
    Type
    string
    Description

    The language the layout document itself is written in — the ontology's default language, e.g. en. Never a key of translations.

  • Name
    composition
    Type
    object
    Description

    The layout document — a JSON object describing how the public passport page is presented (see below). Null until you set it.

  • Name
    translations
    Type
    object
    Description

    Every other language the layout exists in, keyed by language code, each a flat map of string key → text. {} when the layout has not been translated.

  • Name
    metadata
    Type
    object
    Description

    Caller-owned metadata bag. Always present, {} when empty.

  • Name
    created_at
    Type
    string
    Description

    ISO 8601 timestamp of creation.

  • Name
    updated_at
    Type
    string
    Description

    ISO 8601 timestamp of the last update.

compositions:read and compositions:write can be restricted to specific ontologies, and both sit inside ontologies:read: a key cannot hold them without it, nor reach an ontology its ontologies:read does not.

Compositions carry their own composition.created / composition.updated events — a layout change does not surface as passport.updated or ontology.updated. Translation writes emit composition.updated too, with the full resource, translations included.

The layout document

composition is a JSON object that describes the public presentation: the masthead, the tabs, the cards on each tab, the sections in each card, and the properties each section places. It controls presentation only; the underlying schema lives on the ontology and its passports.

The API stores the document as sent and echoes it back verbatim, but it checks the parts the viewer reads: that it is an object — a bare array, string, number, or null is rejected with 400 — that it stays under 400 KB and 31 levels of nesting, that each of its strings is within its cap, and that its look (the design block, the masthead's copy, section options and each placement's display) fits the contract. Anything else in the document is yours to shape. PATCH replaces the whole document, so send the complete layout you want each time.

composition

{
  "version": 2,
  "masthead": {
    "name": { "source": "property", "value": "", "property_id": "property_serial", "node_id": "B" },
    "logo": { "snowflake": null },
    "image": { "snowflake": null },
    "fields": []
  },
  "tabs": [
    {
      "id": "tab_overview", "name": "overview", "label": "Overview", "icon": null,
      "cards": [
        {
          "id": "card_specification", "type": "standard", "title": "Pack specification",
          "description": "", "size": "full", "config": {},
          "sections": [
            {
              "id": "section_key_figures", "type": "fields", "title": "Key figures", "options": {},
              "properties": [
                { "id": "ref_capacity", "property_id": "property_capacity", "node_id": "B",
                  "type": "measurement", "label": "Nominal Capacity",
                  "label_override": null, "show_label": true }
              ]
            }
          ]
        }
      ]
    }
  ]
}

Every tab, card, section, masthead field and placement carries a stable id; translations are keyed by those ids, so a translated string survives reordering and relabeling. A placement names an ontology property by property_id and the level it lives on by node_id, and its label_override replaces the property's name in that one place. A section's type is fields (label/value rows), grid (stat tiles), media (images and files), text (static markdown in options.content), or chart.

Ontologies without a stored composition still render — the viewer synthesizes a default layout from the passport data. See the Data model for how passports are assembled.

Chart sections

A chart section draws one chart from the stored values of the numeric properties it places:

chart section

{
  "id": "section_cell_voltage", "type": "chart", "title": "Cell voltage",
  "options": { "chart_type": "bar" },
  "properties": [
    { "id": "ref_cell_voltage", "property_id": "property_cell_voltage", "node_id": "C",
      "type": "measurement", "label": "Cell Voltage",
      "label_override": null, "show_label": true }
  ]
}
  • options.chart_type is pie, bar, line or meter. A meter chart draws each point as a labelled bar on a track, filled up to options.meter_max (a number above 0, up to 1,000,000,000; 100 when unset); a point beyond the scale prints its real value and fills the whole track. Any other chart_type is refused on write, and a layout stored before that check with one still draws as bar.
  • It plots number, measurement and composite properties, plus range in a bar chart, which draws a span as a floating band. A pie skips values that are not positive.
  • Placements are not confined to one level: they may name a property on any level of the passport's discoverable tree. A placement on a many level contributes one point per child passport, for the first 25 children.

Each point is an ordinary passport value, so it can arrive by import and be queried like any other. The API checks a chart's chart_type and meter_max (see Validation), not which properties it places.

Translations

The layout document is written in the ontology's default language, reported as default_language. Every other language lives in translations, keyed by language code, as a flat map from string key to text:

translations

{
  "de": {
    "tab.tab_overview.label": "Übersicht",
    "card.card_specification.title": "Pack-Spezifikation",
    "section.section_recycling.content": "## Rücknahme\n\nAn jeder Sammelstelle abgeben.",
    "property.property_capacity": "Nennkapazität"
  }
}

A key names one slot by the layout's own ids:

KeySlot
masthead.nameThe masthead name, when its source is static
masthead.kickerThe short line above the masthead name
masthead.captionThe caption on the product image
masthead.noteThe closing note at the end of the passport
masthead.field.<id>.labelA masthead field's label
masthead.field.<id>.valueA masthead field's static value
tab.<id>.labelA tab's label
card.<id>.titleA card's title
card.<id>.descriptionA card's description
section.<id>.titleA section's heading
section.<id>.contentA text section's markdown
ref.<id>.labelA placement's label_override
property.<property_id>An ontology property's name, as the public passport shows it

Every value is capped like a short_text value — 255 characters — except section.<id>.content and masthead.note, which are capped like a long_text at 7,000; either also stops at 14,000 bytes. The key's shape is validated, not whether its id still exists in the layout: a key for a deleted card is simply ignored when rendering.

Languages are the 24 official EU languages, by ISO 639-1 code: bg, cs, da, de, el, en, es, et, fi, fr, ga, hr, hu, it, lt, lv, mt, nl, pl, pt, ro, sk, sl, sv. A regional tag such as de-AT is accepted and normalized to its base language; anything else is 400 language_invalid.

The public passport viewer resolves each string the way it resolves a passport value — the reader's language, then the default, then any translation present.

Looks and component styles

A layout can carry its own look. It is all optional: a layout that says nothing about it follows the team's design.

The design block

design overrides the team's design for the passports of this ontology. It takes any setting of the design model but logo, each nullable, and null inherits:

design

{
  "design": {
    "preset": "editorial",
    "mode": "advanced",
    "hero": "centered",
    "font_heading": "Playfair Display",
    "components": { "image": "framed", "figure": "meter" },
    "chapters": { "header": "numeral", "show": { "icon": false } }
  }
}

The settings are palette, rounding, shadow, font, preset, mode, color_mode, appearance, colors, font_heading, font_figures, labels, navigation, hero, frames, components, component_options and the eight part-style blocks top_bar, masthead, chapters, sections, fields, figures, charts and rhythm, with the values and the font catalogue of the design guide.

A setting the block leaves out or sets to null falls through to the team's design, then to the preset, in that order, and each part-style leaf does so one by one (see Resolution order). A block here is a whole object, as it is on the team's design: the leaves it does not name are unset, not kept from an earlier save. A short palette is completed from its brand colour as on the team's design.

mode follows the same order, and a block with no mode that sets an advanced setting (frames, hero, components, a part style, anything the mode gates but shadow and font_figures) counts as advanced. So its settings apply even over a team in simple, and, as with a block set to advanced, the advanced values the team has stored come back too; set mode: "simple" in the block to keep them all off (see The mode).

An unknown key, logo included, is refused rather than ignored. The block is part of the layout, so a PATCH of the composition replaces it with the rest.

Placements: display and display_options

A placement in a section's properties takes two optional fields:

  • display: one style of its value's kind (the table under Components), or null to inherit the default for that kind from the composition's, then the team's components.
  • display_options: options of that style, each only where the style uses it. An image placement's aspect and fit win over the design's component_options.image, key by key.
OptionUsed byValues
aspectplain, captioned, framed imagesoriginal, 1:1, 4:3, 3:2, 16:9
fitplain, captioned, framed imagescover, contain
widthplain, captioned, framed imagesinline, full
maxmeter, ringA number above 0, up to 1,000,000,000; a full bar or ring stands for it

placement

{ "id": "ref_capacity", "property_id": "property_capacity", "node_id": "B", "type": "measurement",
  "display": "ring", "display_options": { "max": 120 } }

A value over max still prints in full; only the drawing stops at the end of the bar. A placement of a calculation ("source": "calculation") takes the styles of a number (figure, inline, meter, ring) or of a yes/no (text, badge).

The API judges a placement by its own type field, which the Composer writes with every property it places: a display that is not a style of that type's kind is refused, and so is any display on a type that has no kind (short text and date have one look). A placement with no type is stored as sent and not checked.

Field List styles

A Field List (a section of type fields) takes options.style: rows (the default, label and value on a line) or timeline (a vertical rule with a dot at each row, for process or supply-chain steps). How the rows themselves are drawn is the design's fields block. A chart's meter type is under Chart sections.

The masthead's copy

The masthead takes three optional texts, or null: kicker, a short line above the name; caption, a caption on the product image; and note, a closing note at the end of the passport. They are capped like other strings, 255 characters for kicker and caption and 7,000 for note, and translated under masthead.kicker, masthead.caption and masthead.note.

masthead

{ "masthead": { "kicker": "Edition 01", "caption": "The 48 V module, 2026", "note": "Returned packs are recycled in the EU." } }

Validation

A bad look is 400 parameter_invalid, with one error per slot, keyed by its path in the document, and param naming the first:

Error

{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_invalid",
    "message": "Pick one of: figure, inline, meter, ring.",
    "param": "composition.tabs.0.cards.1.sections.0.properties.2.display",
    "errors": {
      "composition.tabs.0.cards.1.sections.0.properties.2.display": ["Pick one of: figure, inline, meter, ring."]
    }
  }
}

Each of these is refused, under the slot named:

RefusedSlot
A design that is not an object, an unknown key in it, or a setting outside the contract: a preset not in the list, a font outside the catalogue, a colour that is not #rrggbb, a rounding outside 1 to 6, a components style that does not belong to its kind, a component_options kind other than image or an option or value outside its list, a part-style block that is not an object, a leaf or show part it does not have, a value outside its listcomposition.design, composition.design.<key>, composition.design.colors.<role>, composition.design.components.<kind>, composition.design.component_options.image.<option>, composition.design.chapters.header, composition.design.masthead.show.ids
A masthead kicker, caption or note that is not text, or is over its capcomposition.masthead.kicker, .caption, .note
A Field List options.style that is not rows or timelinecomposition.tabs.<t>.cards.<c>.sections.<s>.options.style
A chart options.chart_type outside the four, or an options.meter_max that is not a number above 0, up to 1,000,000,000composition.tabs.<t>.cards.<c>.sections.<s>.options.chart_type, .meter_max
A placement display that does not fit its value, or a display_options that is not an object, holds a key the display does not use (any key, when display is null) or a value outside the listcomposition.tabs.<t>.cards.<c>.sections.<s>.properties.<r>.display, .display_options, .display_options.<option>

GET/v1/ontologies/:ontology/composition

Retrieve a composition

Retrieves the composition for an ontology, with default_language and every other language's strings under translations. Returns 404 composition_missing until a composition has been created — ontologies without one still render from a synthesized default layout. Requires compositions:read.

Request

GET
/v1/ontologies/ontology_7d1f4a2b9c3e5081/composition
curl https://api.synexcloud.com/v1/ontologies/ontology_7d1f4a2b9c3e5081/composition \
  -H "Authorization: Bearer $SYNEX_API_KEY"

Response

{
  "object": "composition",
  "id": "ontology_7d1f4a2b9c3e5081",
  "ontology": "ontology_7d1f4a2b9c3e5081",
  "name": "EV Battery Pack",
  "description": "Traction packs and cells for the 2026 EU battery passport programme.",
  "default_language": "en",
  "composition": {
    "version": 2,
    "masthead": {
      "name": { "source": "property", "value": "",
                "property_id": "property_serial", "node_id": "B" },
      "logo": { "snowflake": null },
      "image": { "snowflake": null },
      "fields": []
    },
    "tabs": [
      {
        "id": "tab_overview", "name": "overview", "label": "Overview", "icon": null,
        "cards": [
          {
            "id": "card_specification", "type": "standard",
            "title": "Pack specification", "description": "",
            "size": "full", "config": {},
            "sections": [
              {
                "id": "section_key_figures", "type": "fields",
                "title": "Key figures", "options": {},
                "properties": [
                  { "id": "ref_capacity", "property_id": "property_capacity",
                    "node_id": "B", "type": "measurement",
                    "label": "Nominal Capacity",
                    "label_override": null, "show_label": true }
                ]
              }
            ]
          }
        ]
      }
    ]
  },
  "translations": {
    "de": {
      "tab.tab_overview.label": "Übersicht",
      "card.card_specification.title": "Pack-Spezifikation",
      "property.property_capacity": "Nennkapazität"
    }
  },
  "metadata": { "owner_team": "packaging" },
  "created_at": "2026-07-21T11:06:40Z",
  "updated_at": "2026-07-22T09:31:12Z"
}

POST/v1/ontologies/:ontology/composition

Create a composition

Creates the single composition for an ontology and returns it with 201. Its name and description are copied from the ontology, and the layout starts empty — shape it with update. The body can carry only metadata, and may be omitted entirely. Fails with 409 composition_exists if the ontology already has one. Requires compositions:write. Accepts an optional Idempotency-Key header.

Optional attributes

  • Name
    metadata
    Type
    object
    Description

    Initial metadata bag.

Request

POST
/v1/ontologies/ontology_7d1f4a2b9c3e5081/composition
curl -X POST https://api.synexcloud.com/v1/ontologies/ontology_7d1f4a2b9c3e5081/composition \
  -H "Authorization: Bearer $SYNEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"metadata": {"owner_team": "packaging"}}'

Response

{
  "object": "composition",
  "id": "ontology_7d1f4a2b9c3e5081",
  "ontology": "ontology_7d1f4a2b9c3e5081",
  "name": "EV Battery Pack",
  "description": "Traction packs and cells for the 2026 EU battery passport programme.",
  "default_language": "en",
  "composition": null,
  "translations": {},
  "metadata": { "owner_team": "packaging" },
  "created_at": "2026-07-21T11:06:40Z",
  "updated_at": "2026-07-21T11:06:40Z"
}

PATCH/v1/ontologies/:ontology/composition

Update a composition

Replaces the layout document, merges metadata, or both. Each is optional, but the body must carry at least one — an empty body is 400 parameter_invalid naming composition. 404 composition_missing until one has been created. Requires compositions:write.

composition overwrites the whole document — send the complete layout you want, not a partial patch. It must be a JSON object; a bare array, string, number, or null is rejected with 400, as is a document nested deeper than 31 levels.

Every string in the layout is capped like its translations: 255 characters for a label, a heading or the masthead's kicker and caption, 7,000 for a text section's content or the masthead's note. An over-cap string is 400 parameter_invalid naming its slot — composition.tab.tab_overview.label, say.

The layout's look is checked too: its design block, the masthead's copy, a Field List's options.style, a chart's options.chart_type and options.meter_max, and each placement's display and display_options. Each bad slot is a 400 parameter_invalid keyed by its path in the document, and an unknown key in design or display_options is refused rather than ignored. See Validation.

Optional attributes

  • Name
    composition
    Type
    object
    Description

    The full replacement layout document. Replaces the existing document entirely.

  • Name
    metadata
    Type
    object
    Description

    Merged per key; a null value deletes that key.

Request

PATCH
/v1/ontologies/ontology_7d1f4a2b9c3e5081/composition
curl -X PATCH https://api.synexcloud.com/v1/ontologies/ontology_7d1f4a2b9c3e5081/composition \
  -H "Authorization: Bearer $SYNEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "composition": {
      "version": 2,
      "masthead": {
        "name": { "source": "property", "value": "", "property_id": "property_serial", "node_id": "B" },
        "logo": { "snowflake": null },
        "image": { "snowflake": null },
        "fields": []
      },
      "tabs": [
        { "id": "tab_overview", "name": "overview", "label": "Overview", "icon": null,
          "cards": [
            { "id": "card_specification", "type": "standard", "title": "Pack specification",
              "description": "", "size": "full", "config": {},
              "sections": [
                { "id": "section_key_figures", "type": "fields", "title": "Key figures", "options": {},
                  "properties": [
                    { "id": "ref_capacity", "property_id": "property_capacity", "node_id": "B",
                      "type": "measurement", "label": "Nominal Capacity",
                      "label_override": null, "show_label": true }
                  ] }
              ] }
          ] }
      ]
    }
  }'

Response

{
  "object": "composition",
  "id": "ontology_7d1f4a2b9c3e5081",
  "ontology": "ontology_7d1f4a2b9c3e5081",
  "name": "EV Battery Pack",
  "description": "Traction packs and cells for the 2026 EU battery passport programme.",
  "default_language": "en",
  "composition": {
    "version": 2,
    "masthead": {
      "name": { "source": "property", "value": "",
                "property_id": "property_serial", "node_id": "B" },
      "logo": { "snowflake": null },
      "image": { "snowflake": null },
      "fields": []
    },
    "tabs": [
      {
        "id": "tab_overview", "name": "overview", "label": "Overview", "icon": null,
        "cards": [
          {
            "id": "card_specification", "type": "standard",
            "title": "Pack specification", "description": "",
            "size": "full", "config": {},
            "sections": [
              {
                "id": "section_key_figures", "type": "fields",
                "title": "Key figures", "options": {},
                "properties": [
                  { "id": "ref_capacity", "property_id": "property_capacity",
                    "node_id": "B", "type": "measurement",
                    "label": "Nominal Capacity",
                    "label_override": null, "show_label": true }
                ]
              }
            ]
          }
        ]
      }
    ]
  },
  "translations": {},
  "metadata": { "owner_team": "packaging" },
  "created_at": "2026-07-21T11:06:40Z",
  "updated_at": "2026-07-22T09:31:12Z"
}

PUT/v1/ontologies/:ontology/composition/translations/:lang

Replace a translation

Replaces every string the layout has in :lang with the map you send — a key you leave out is removed, and an empty map removes the language entirely. Returns the whole composition. Requires compositions:write.

:lang is one of the 24 EU language codes. The default language is refused with 400 language_is_default — its strings are the layout document itself, so edit that with update instead.

Each key must be one of the string keys and each value a string within its cap. A body with problems is 400 translation_invalid: error.param is strings, and error.errors names every offending key. One language's strings must together stay under 400 KB, or the write is 413 composition_too_large. 404 composition_missing until a composition exists. Emits composition.updated.

Required attributes

  • Name
    strings
    Type
    object
    Description

    The language's strings — a flat map of string key → text. Replaces whatever the language held before.

Request

PUT
/v1/ontologies/ontology_7d1f4a2b9c3e5081/composition/translations/de
curl -X PUT https://api.synexcloud.com/v1/ontologies/ontology_7d1f4a2b9c3e5081/composition/translations/de \
  -H "Authorization: Bearer $SYNEX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "strings": {
      "tab.tab_overview.label": "Übersicht",
      "card.card_specification.title": "Pack-Spezifikation",
      "property.property_capacity": "Nennkapazität"
    }
  }'

Response

{
  "object": "composition",
  "id": "ontology_7d1f4a2b9c3e5081",
  "ontology": "ontology_7d1f4a2b9c3e5081",
  "name": "EV Battery Pack",
  "description": "Traction packs and cells for the 2026 EU battery passport programme.",
  "default_language": "en",
  "composition": {
    "version": 2,
    "masthead": { "name": { "source": "property", "value": "",
                            "property_id": "property_serial", "node_id": "B" } },
    "tabs": [ { "id": "tab_overview", "name": "overview", "label": "Overview" } ]
  },
  "translations": {
    "de": {
      "tab.tab_overview.label": "Übersicht",
      "card.card_specification.title": "Pack-Spezifikation",
      "property.property_capacity": "Nennkapazität"
    }
  },
  "metadata": { "owner_team": "packaging" },
  "created_at": "2026-07-21T11:06:40Z",
  "updated_at": "2026-07-22T09:31:12Z"
}

DELETE/v1/ontologies/:ontology/composition/translations/:lang

Delete a translation

Removes every string the layout has in :lang. Removing a language that has none returns the same acknowledgement. The default language is accepted here — a stored copy of it is inert, and this is how it is tidied up. 404 composition_missing until a composition exists. Emits composition.updated. Requires compositions:write.

Request

DELETE
/v1/ontologies/ontology_7d1f4a2b9c3e5081/composition/translations/de
curl -X DELETE https://api.synexcloud.com/v1/ontologies/ontology_7d1f4a2b9c3e5081/composition/translations/de \
  -H "Authorization: Bearer $SYNEX_API_KEY"

Response

{
  "object": "composition.translation",
  "ontology": "ontology_7d1f4a2b9c3e5081",
  "language": "de",
  "deleted": true
}

Was this page helpful?