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 oftranslations.
- 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_typeispie,bar,lineormeter. Ameterchart draws each point as a labelled bar on a track, filled up tooptions.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 otherchart_typeis refused on write, and a layout stored before that check with one still draws asbar.- It plots
number,measurementandcompositeproperties, plusrangein 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
manylevel 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:
| Key | Slot |
|---|---|
masthead.name | The masthead name, when its source is static |
masthead.kicker | The short line above the masthead name |
masthead.caption | The caption on the product image |
masthead.note | The closing note at the end of the passport |
masthead.field.<id>.label | A masthead field's label |
masthead.field.<id>.value | A masthead field's static value |
tab.<id>.label | A tab's label |
card.<id>.title | A card's title |
card.<id>.description | A card's description |
section.<id>.title | A section's heading |
section.<id>.content | A text section's markdown |
ref.<id>.label | A 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'scomponents.display_options: options of that style, each only where the style uses it. An image placement'saspectandfitwin over the design'scomponent_options.image, key by key.
| Option | Used by | Values |
|---|---|---|
aspect | plain, captioned, framed images | original, 1:1, 4:3, 3:2, 16:9 |
fit | plain, captioned, framed images | cover, contain |
width | plain, captioned, framed images | inline, full |
max | meter, ring | A 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:
| Refused | Slot |
|---|---|
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 list | composition.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 cap | composition.masthead.kicker, .caption, .note |
A Field List options.style that is not rows or timeline | composition.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,000 | composition.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 list | composition.tabs.<t>.cards.<c>.sections.<s>.properties.<r>.display, .display_options, .display_options.<option> |
A layout saved before these checks that holds one of these is refused the next time it is sent back unchanged: read it, fix the slots the error names, and send it again. Reading a composition is unaffected.
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
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"
}
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
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"
}
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.
The stored layout must stay under 400 KB. A larger document is
rejected with 413 composition_too_large rather than a server error —
trim the layout and retry.
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
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"
}
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
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 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
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
}