Ontologies
An ontology is your team's live schema — a completed template instantiated for real product data. Creating one deep-copies the template's structure (regenerating every property id) and automatically creates the root passport at the top level, so you can start adding data immediately. There's no separate "activate" step: an ontology is live the moment it's created.
Creating an ontology regenerates every property id. Always read property
ids from the ontology's levels (below), never from the template you started
with — they won't match.
The ontology model
- Name
object- Type
- string
- Description
Always
ontology.
- Name
id- Type
- string
- Description
Unique identifier, e.g.
ontology_00a1b2c3d4e5f607.
- Name
name- Type
- string
- Description
Display name (3–255 characters). Nullable.
- Name
description- Type
- string
- Description
Optional description (up to 1000 characters). Nullable.
- Name
template- Type
- string
- Description
The id of the template this ontology was instantiated from. Nullable.
- Name
levels- Type
- array
- Description
The schema tree — an array of level objects (see below), always the latest version's. This is your reference for level keys and property ids.
- Name
latest_version- Type
- integer
- Description
The highest published version number.
- Name
has_draft- Type
- boolean
- Description
Whether an open draft exists.
- 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.
The level object
Each entry in levels describes one level of the schema. See the Data model for how the type values behave.
- Name
key- Type
- string
- Description
Level key —
Ais the root; children follow breadth-first (B,C, …). This is what you pass aslevelwhen creating passports and imports.
- Name
name- Type
- string
- Description
Human-readable level name. Nullable.
- Name
type- Type
- string
- Description
singular,series,many, orcomposite. Nullable.
- Name
is_root- Type
- boolean
- Description
Whether this is the root level.
- Name
static_lineage- Type
- boolean
- Description
Whether every ancestor of this level is singular (affects how paths are encoded). Nullable.
- Name
parent- Type
- string
- Description
The parent level key, or
nullfor the root.
- Name
children- Type
- array
- Description
Child level keys.
- Name
discoverable- Type
- array
- Description
The child levels traversed when rendering the public passport (series children are excluded).
- Name
properties- Type
- array
- Description
Property definitions — the same shape as a template's property definitions:
id,name,type, and value constraints. Theidis what you use in passportvaluesand import mappings.
List ontologies
Lists your team's ontologies as a paginated list. Requires ontologies:read.
Optional attributes
- Name
created[gte]- Type
- string
- Description
Only ontologies created at or after this time. Unix seconds or an ISO date, inclusive of the whole second named.
- Name
created[lte]- Type
- string
- Description
Only ontologies created at or before this time. An inverted range is
400.
- Name
limit- Type
- integer
- Description
Page size, 1–100 (default 10).
- Name
cursor- Type
- string
- Description
A pagination cursor from a previous response's
next_cursor.
The creation window runs as a true key condition, so pages stay exact — this list never comes back short because of the filter. Filters join the cursor's scope; see pagination.
Request
curl -G https://api.synexcloud.com/v1/ontologies \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-d limit=10
Response
{
"object": "list",
"data": [
{
"object": "ontology",
"id": "ontology_00a1b2c3d4e5f607",
"name": "EV Battery Line A",
"description": "2026 production",
"template": "template_00a1b2c3d4e5f607",
"levels": [
{ "key": "A", "name": "SKU", "type": "singular", "is_root": true }
],
"created_at": "2026-07-10T12:00:00Z",
"updated_at": "2026-07-10T12:00:00Z"
}
],
"has_more": false,
"next_cursor": null,
"url": "/v1/ontologies"
}
Create an ontology
Instantiates a completed template — yours or a public one — into a live ontology. The root passport is created automatically and returned as root_passport; use its id as the parent for your first child passports. Draft templates are rejected with 409 template_is_draft. Requires ontologies:write. Accepts an optional Idempotency-Key header.
Required attributes
- Name
template- Type
- string
- Description
The completed template's id.
- Name
name- Type
- string
- Description
Display name, 3–255 characters.
Optional attributes
- Name
description- Type
- string
- Description
Description, up to 1000 characters.
Request
curl https://api.synexcloud.com/v1/ontologies \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "template": "template_00a1b2c3d4e5f607", "name": "EV Battery Line A", "description": "2026 production" }'
Response
{
"object": "ontology",
"id": "ontology_00a1b2c3d4e5f607",
"name": "EV Battery Line A",
"description": "2026 production",
"template": "template_00a1b2c3d4e5f607",
"levels": [
{ "key": "A", "name": "SKU", "type": "singular", "is_root": true,
"children": ["B"], "properties": [] },
{ "key": "B", "name": "Pack", "type": "series", "parent": "A",
"properties": [
{ "id": "property_9f2k7d", "name": "Capacity", "type": "measurement" }
] }
],
"root_passport": {
"object": "passport",
"id": "pass_00a1b2c3d4e5f607",
"ontology": "ontology_00a1b2c3d4e5f607",
"parent": null,
"level": "A",
"path": "A",
"publish_status": null,
"values": {}
},
"created_at": "2026-07-10T12:00:00Z",
"updated_at": "2026-07-10T12:00:00Z"
}
Retrieve an ontology
Returns the ontology including its full levels — level keys, types, and property definitions. This is where you read the property ids you'll write to in passport values and reference in import mappings. Requires ontologies:read.
levels here is always the latest version's. To resolve an older passport's schema, read its pinned version with GET /v1/ontologies/{ontology}/versions/{version}.
Request
curl https://api.synexcloud.com/v1/ontologies/ontology_00a1b2c3d4e5f607 \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "ontology",
"id": "ontology_00a1b2c3d4e5f607",
"name": "EV Battery Line A",
"description": "2026 production",
"template": "template_00a1b2c3d4e5f607",
"levels": [
{
"key": "A", "name": "SKU", "type": "singular", "is_root": true,
"static_lineage": true, "parent": null, "children": ["B"],
"discoverable": ["B"], "properties": []
},
{
"key": "B", "name": "Pack", "type": "series", "is_root": false,
"static_lineage": true, "parent": "A", "children": ["C"],
"discoverable": ["A"],
"properties": [
{ "id": "property_9f2k7d", "name": "Capacity", "type": "measurement" }
]
}
],
"latest_version": 2,
"has_draft": false,
"metadata": {},
"created_at": "2026-07-10T12:00:00Z",
"updated_at": "2026-07-10T12:00:00Z"
}
Update an ontology
Updates the metadata bag and nothing else. Requires ontologies:write.
That is the whole endpoint. An ontology's name, description and structure come from its template and change only through the draft/version lifecycle, where every edit is checked against the passports already issued under the old schema. This is not a second, unversioned way in.
metadata must be present in the body — an empty body is 400 parameter_invalid naming metadata — so send {} for a deliberate no-op.
Required attributes
- 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_00a1b2c3d4e5f607 \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"metadata": {"erp_line": "L-14"}}'
Response
{
"object": "ontology",
"id": "ontology_00a1b2c3d4e5f607",
"name": "EV Battery Line A",
"description": "2026 production",
"template": "template_00a1b2c3d4e5f607",
"levels": [
{ "key": "A", "name": "SKU", "type": "singular", "is_root": true }
],
"latest_version": 2,
"has_draft": false,
"metadata": { "erp_line": "L-14" },
"created_at": "2026-07-10T12:00:00Z",
"updated_at": "2026-07-28T14:02:19Z"
}
Versioning
A schema evolves without breaking the passports already issued under it. Versions are append-only: publishing adds version N+1 and never alters or removes an existing one.
Every passport is pinned to the version it was created under, exposed as ontology_version. That pin decides two things:
- Reads render the passport against its own version's levels — its
discoverable_levels, discovery chain and property definitions are the ones that existed when it was issued. PATCH /v1/passports/{passport}validates values against its own version's property definitions. A property added in a later version is rejected on an older-pinned passport.
Creation surfaces use the latest version instead. Creating a passport or
starting an import validates against latest_version, and the new
passport pins that version. This is deliberate: it means a level added in a
new version is immediately creatable under a parent still pinned to an old
one.
Changes must be additive
The draft is validated against the published version before it can be saved or published. A violation is 400 structure_invalid, with one message per violation in error.errors.structure:
- published levels cannot be deleted, re-parented, re-typed, or re-lettered;
- a published parent/child edge cannot be dropped;
- a property carried over from a published version must keep its
id; - the graph stays a single tree rooted at
A.
Adding levels and adding, editing or removing properties are all fine.
Level keys are assigned by the server
This is the part that most often surprises people. You do not choose level keys, and you do not choose ids for new properties.
- Send any placeholder key you like for a new level. The server assigns the next unused letter —
C,D, …AA— and returns it in the response'slevels. Read the real key back from there; the placeholder is gone. - A key that already exists in the published schema or the current draft is kept as-is. Existing levels are never re-lettered — every passport path in the system depends on that.
- One trap: a placeholder that looks like a level key (
^[A-Z]+$) but is not one of the known keys is treated as a placeholder and silently re-lettered. Use obviously non-key placeholders ("new-module","tmp-1") so you never have to think about it. - Property ids work the same way: omit
idfor a new property and one is minted; carry an existingidthrough verbatim to keep the values already stored against it.
Concurrency
Three guards, three different 409s:
| Code | Meaning | What to do |
|---|---|---|
draft_exists | POST /draft when one is already open | GET it, or DELETE it first |
draft_conflict | PATCH /draft with a stale base_updated_at | Re-read the draft, reapply your changes |
version_conflict | publish with a stale expected_latest_version | Re-read latest_version and retry |
base_updated_at is an opaque token, not a timestamp you should read. Echo it back exactly as received; do not parse, reformat, or compare it. Every draft response carries a fresh one.
PATCH /draft is deliberately not covered by idempotency keys — base_updated_at is already its concurrency control. The two POSTs are: note that a replayed POST /draft returns the stored response, whose base_updated_at may since have gone stale, so if a following PATCH returns draft_conflict, re-read the draft with GET.
Ontologies from before versioning. An ontology whose stored schema
predates versioning reports latest_version: 1 and a single version 1 with a
null published_at. You can open and edit a draft on it, but publishing is
refused with 409 ontology_not_migrated until it has been backfilled.
Ontology and version mutations do not emit audit events. Publishing does emit ontology.version.published on the change feed.
List versions
Every published version of the ontology. Requires ontologies:read.
Versions are immutable once published, so this list only ever grows.
Request
curl https://api.synexcloud.com/v1/ontologies/ontology_00a1b2c3d4e5f607/versions \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "list",
"data": [
{
"object": "ontology.version",
"ontology": "ontology_00a1b2c3d4e5f607",
"version": 1,
"status": "published",
"published_at": "2026-07-10T12:00:00Z",
"levels": [
{ "key": "A", "name": "SKU", "type": "singular", "is_root": true }
]
},
{
"object": "ontology.version",
"ontology": "ontology_00a1b2c3d4e5f607",
"version": 2,
"status": "published",
"published_at": "2026-07-24T08:31:47Z",
"levels": [
{ "key": "A", "name": "SKU", "type": "singular", "is_root": true },
{ "key": "C", "name": "Module", "type": "many", "parent": "A" }
]
}
],
"has_more": false,
"next_cursor": null,
"url": "/v1/ontologies/ontology_00a1b2c3d4e5f607/versions"
}
Retrieve a version
One published version, with the schema exactly as passports pinned to it read it. Requires ontologies:read.
This is how you resolve an older passport's schema: take its ontology_version and read that version's levels.
Request
curl https://api.synexcloud.com/v1/ontologies/ontology_00a1b2c3d4e5f607/versions/1 \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "ontology.version",
"ontology": "ontology_00a1b2c3d4e5f607",
"version": 1,
"status": "published",
"published_at": "2026-07-10T12:00:00Z",
"levels": [
{
"key": "A", "name": "SKU", "type": "singular", "is_root": true,
"static_lineage": true, "parent": null, "children": ["B"],
"discoverable": ["B"], "properties": []
},
{
"key": "B", "name": "Pack", "type": "series", "is_root": false,
"static_lineage": true, "parent": "A", "children": [],
"discoverable": ["A"],
"properties": [
{ "id": "property_9f2k7d", "name": "Capacity", "type": "measurement" }
]
}
]
}
The draft lifecycle
An ontology has at most one open draft at a time, always based on the latest published version.
- Open a draft — a copy of the latest published version.
- Edit it, sending the complete level map each time.
- Publish it as the next version, or delete it to throw the work away.
Deleting a draft never affects published versions.
Retrieve the draft
Reads the open draft. Requires ontologies:read. 404 draft_missing when none is open.
Every draft response carries a fresh base_updated_at — echo it back verbatim on your next PATCH.
Request
curl https://api.synexcloud.com/v1/ontologies/ontology_00a1b2c3d4e5f607/draft \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "ontology.draft",
"ontology": "ontology_00a1b2c3d4e5f607",
"status": "draft",
"created_from_version": 2,
"base_updated_at": "1753699847.482913",
"latest_version": 2,
"levels": [
{ "key": "A", "name": "SKU", "type": "singular", "is_root": true,
"children": ["B", "C"], "properties": [] },
{ "key": "C", "name": "Module", "type": "many", "parent": "A",
"properties": [
{ "id": "property_4c8b1e", "name": "Slot", "type": "short_text" }
] }
]
}
Open a draft
Copies the latest published version into a new draft. Requires ontologies:write. Accepts an optional Idempotency-Key header.
409 draft_exists if one is already open — GET it, or DELETE it first.
A replayed POST returns the stored response, whose base_updated_at may since have gone stale. If a following PATCH returns draft_conflict, re-read the draft with GET.
Request
curl -X POST https://api.synexcloud.com/v1/ontologies/ontology_00a1b2c3d4e5f607/draft \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "ontology.draft",
"ontology": "ontology_00a1b2c3d4e5f607",
"status": "draft",
"created_from_version": 2,
"base_updated_at": "1753699847.482913",
"latest_version": 2,
"levels": [
{ "key": "A", "name": "SKU", "type": "singular", "is_root": true,
"children": ["B"], "properties": [] },
{ "key": "B", "name": "Pack", "type": "series", "parent": "A",
"properties": [
{ "id": "property_9f2k7d", "name": "Capacity", "type": "measurement" }
] }
]
}
Edit the draft
Replaces the draft's structure. Requires ontologies:write.
Send the complete level map, not a partial patch. structure replaces
the draft's whole tree — a level you omit is a level you deleted, which
the additive-change validator will reject if it was already published.
Changes are validated against the published version on every save, so a violation surfaces here rather than at publish time.
Required attributes
- Name
base_updated_at- Type
- string
- Description
The opaque token from the previous draft response, echoed verbatim. A mismatch is
409 draft_conflict.
- Name
structure- Type
- object
- Description
The complete level map, keyed by level key. New levels may use any placeholder key — the server assigns the real one.
Request
curl -X PATCH https://api.synexcloud.com/v1/ontologies/ontology_00a1b2c3d4e5f607/draft \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"base_updated_at": "1753699847.482913",
"structure": {
"A": {"name": "SKU", "type": "singular", "is_root": true,
"properties": [], "children": ["B", "new-module"]},
"B": {"name": "Pack", "type": "series", "children": [],
"properties": [
{"id": "property_9f2k7d", "name": "Capacity", "type": "measurement"}
]},
"new-module": {"name": "Module", "type": "many", "children": [],
"properties": [{"name": "Slot", "type": "short_text"}]}
}
}'
Response
{
"object": "ontology.draft",
"ontology": "ontology_00a1b2c3d4e5f607",
"status": "draft",
"created_from_version": 2,
"base_updated_at": "1753700114.907255",
"latest_version": 2,
"levels": [
{ "key": "A", "name": "SKU", "type": "singular", "is_root": true,
"children": ["B", "C"], "properties": [] },
{ "key": "B", "name": "Pack", "type": "series", "parent": "A",
"children": [],
"properties": [
{ "id": "property_9f2k7d", "name": "Capacity", "type": "measurement" }
] },
{ "key": "C", "name": "Module", "type": "many", "parent": "A",
"children": [],
"properties": [
{ "id": "property_4c8b1e", "name": "Slot", "type": "short_text" }
] }
]
}
The placeholder new-module came back as level C, with a minted property_4c8b1e. Read both from the response — the values you sent are not authoritative.
Discard the draft
Throws the draft away. Requires ontologies:write. Published versions are never affected.
Request
curl -X DELETE https://api.synexcloud.com/v1/ontologies/ontology_00a1b2c3d4e5f607/draft \
-H "Authorization: Bearer $SYNEX_API_KEY"
Response
{
"object": "ontology.draft",
"id": "ontology_00a1b2c3d4e5f607",
"deleted": true
}
Publish the draft
Turns the draft into version N+1 and closes it. Requires ontologies:write. Accepts an optional Idempotency-Key header.
expected_latest_version is the concurrency guard: pass the latest_version you built the draft against, and a mismatch is 409 version_conflict. Re-read latest_version and retry.
Publishing emits ontology.version.published on the change feed; the snapshot carries the version payload including its number.
Existing passports are untouched — they stay pinned to the version they were created under. New passports created from now on pin the new one.
Required attributes
- Name
expected_latest_version- Type
- integer
- Description
The version number you expect to be current. A mismatch is
409 version_conflict.
Request
curl -X POST https://api.synexcloud.com/v1/ontologies/ontology_00a1b2c3d4e5f607/draft/publish \
-H "Authorization: Bearer $SYNEX_API_KEY" \
-H "Content-Type: application/json" \
-d '{"expected_latest_version": 2}'
Response
{
"object": "ontology.version",
"ontology": "ontology_00a1b2c3d4e5f607",
"version": 3,
"status": "published",
"published_at": "2026-07-28T14:20:05Z",
"levels": [
{ "key": "A", "name": "SKU", "type": "singular", "is_root": true,
"children": ["B", "C"], "properties": [] },
{ "key": "B", "name": "Pack", "type": "series", "parent": "A",
"properties": [
{ "id": "property_9f2k7d", "name": "Capacity", "type": "measurement" }
] },
{ "key": "C", "name": "Module", "type": "many", "parent": "A",
"properties": [
{ "id": "property_4c8b1e", "name": "Slot", "type": "short_text" }
] }
]
}