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.

Both ontologies:read and ontologies:write can be restricted to specific ontologies, and ontologies:read is the root every passport, composition and import ability sits inside: a key cannot hold one of those without it, nor reach an ontology its ontologies:read does not. A scoped ontologies:read lists only its ontologies and reads any other as 403 ability_not_scoped_to_resource. A scoped ontologies:write edits the drafts and metadata of its ontologies but cannot create one, because a new ontology is in no scope yet.

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.

An ontology's passport values are authored in its default language — the team's language when the ontology was created, or English if the team has none — and a passport read falls back to it when the language asked for has no translation. It is not a field of this object; the ontology's composition reports it as default_language.

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 — A is the root; children follow breadth-first (B, C, …). This is what you pass as level when creating passports and imports.

  • Name
    name
    Type
    string
    Description

    Human-readable level name. Nullable.

  • Name
    type
    Type
    string
    Description

    singular, series, or many. Nullable.

  • Name
    granularity
    Type
    string
    Description

    Which tier this level reports as to the EU registry — model, batch or item — or null when the level is internal only. See reporting granularity.

  • Name
    is_core
    Type
    boolean
    Description

    Whether this is the level the registry is given as its tier's ID. See which level carries the ID.

  • 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 null for the root.

  • Name
    children
    Type
    array
    Description

    Child level keys.

  • Name
    discoverable
    Type
    array
    Description

    The adjacent levels traversed when rendering the public passport: the singular and many children, plus the parent for every non-root level.

  • Name
    properties
    Type
    array
    Description

    Property definitions — the same shape as a template's property definitions: id, name, type, value constraints, and data_element_id when the property is reported. The id is what you use in passport values and import mappings.


Reporting granularity

A level may declare which of the three tiers it reports as to the EU registry: model, batch or item. null — the default — means the level is internal: it still holds properties and still renders on the passport page, it is simply reported to nobody.

The tiers run coarse to fine, and three rules govern them:

  • Containment is strict. An item sits inside a batch, a batch inside a model. A level advances one tier at a time, and only from the tier directly above it.
  • Only a multiplier opens a finer tier. A singular level is one-to-one with its parent, so it may repeat the tier it inherits but never open a finer one. model is exempt: it establishes the top of the hierarchy rather than subdividing it, so any level type — a singular root included — may open it.
  • Each tier is opened once per ontology. Opening means carrying a tier without inheriting it. Two batches on separate branches would leave an item with no answer to which batch it belongs to. Continuing an inherited run is always fine, however deep — and a level may open a tier that a level below it currently opens, which simply extends that run upwards.

A level with no granularity is transparent: it does not interrupt the run around it. A violation is 400 structure_invalid, with the offending level named in error.errors.structure.

Which level carries the ID

Several levels can share a tier, so one of them has to be the one the registry is given as that tier's ID: the level marked is_core. You choose it — which level of your hierarchy is "the item" is a judgement about your business, not something the shape of the tree settles.

  • A core must carry a granularity.
  • One core per tier. Two would make "the item id" ambiguous.
  • It must sit where every path through the run passes through it — at or above any fork. A core chosen down one branch leaves the others reporting nothing. This is checked when you publish a draft, not on every save, so a part-marked draft still saves. Publishing never picks a core for you; template completion does, for a tier only one level carries.

A passport at a level that carries a tier without being its core reports that tier with a null id. And a series below the core holds many instances per core, so its values cannot be reported as part of the entity the core names.

Reported properties

A level's granularity says which tier it reports as; a property's data_element_id says whether that property is reported at all, and under what name the registry knows it.

property

{ "id": "property_9f2k7d", "name": "Capacity", "type": "measurement", "data_element_id": "BATT-BATCH-CAP" }

Absent is the ordinary state, not an omission: most properties on a reported level are your own business rather than the registry's, so only the ones you map are reported. Nothing requires a mapping, and an ontology with none still publishes.

  • Only a reported level may carry mappings. A data_element_id on a level with no granularity names a field in a submission that never happens, so it is refused rather than silently dropped.
  • One property per element, per tier. The registry receives one submission per tier, so two properties offering values for the same element are ambiguous. Across tiers the same element may be reused: a model's weight and an item's weight are separate submissions.
  • The value has to survive XML and JSON. Valid UTF-8, at most 128 characters, and no control characters — tabs and newlines included, because XML rewrites those inside an attribute value. Markup characters (<, &, quotes) are fine; they escape losslessly. Surrounding whitespace is trimmed on write, and a blank value is stored as no mapping.

A violation is 400 structure_invalid, with the offending property named in error.errors.structure.


GET/v1/ontologies

List ontologies

Lists your team's ontologies as a paginated list. Requires ontologies:read.

A key whose ontologies:read is restricted to specific ontologies gets exactly those. This is the one team-wide list a scoped key may call — it filters rather than refusing — and the creation window, limit and cursor work over the filtered list as they do over the whole team.

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

GET
/v1/ontologies
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",
          "granularity": "model", "is_core": true, "is_root": true }
      ],
      "latest_version": 2,
      "has_draft": false,
      "metadata": {},
      "created_at": "2026-07-10T12:00:00Z",
      "updated_at": "2026-07-24T08:31:47Z"
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "url": "/v1/ontologies"
}

POST/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. Accepts an optional Idempotency-Key header.

Requires an ontologies:write that is not restricted to specific ontologies — a new ontology is in no scope yet, so a scoped one is 403 ability_not_scoped_to_resource. A team without a company (a supplier team) is refused with 403 company_required, because the ontology comes with its root passport and such a team issues none.

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.

  • Name
    metadata
    Type
    object
    Description

    Initial metadata bag.

Request

POST
/v1/ontologies
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", "granularity": null,
      "is_core": false, "is_root": true, "children": ["B"], "properties": [] },
    { "key": "B", "name": "Pack", "type": "series", "granularity": null,
      "is_core": false, "parent": "A",
      "properties": [
        { "id": "property_9f2k7d", "name": "Capacity", "type": "measurement" }
      ] }
  ],
  "latest_version": 1,
  "has_draft": false,
  "metadata": {},
  "root_passport": {
    "object": "passport",
    "id": "pass_00a1b2c3d4e5f607",
    "ontology": "ontology_00a1b2c3d4e5f607",
    "parent": null,
    "level": "A",
    "path": "A",
    "friendly_id": null,
    "ontology_version": 1,
    "publish_status": null,
    "publish_status_resolved": "draft",
    "release_number": 0,
    "values": {},
    "metadata": {},
    "created_at": "2026-07-10T12:00:00Z",
    "updated_at": "2026-07-10T12:00:00Z"
  },
  "created_at": "2026-07-10T12:00:00Z",
  "updated_at": "2026-07-10T12:00:00Z"
}

GET/v1/ontologies/:id

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.

A key whose ontologies:read is restricted to specific ontologies reads only those; any other id is 403 ability_not_scoped_to_resource whether or not it exists — the scope is decided before the lookup. An unscoped key sees 404 for another team's ontology. The same scope check guards every route under the ontology — versions, draft and metadata.

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

GET
/v1/ontologies/ontology_00a1b2c3d4e5f607
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",
      "granularity": "model", "is_core": true, "is_root": true,
      "static_lineage": true, "parent": null, "children": ["B"],
      "discoverable": [], "properties": []
    },
    {
      "key": "B", "name": "Pack", "type": "series",
      "granularity": "batch", "is_core": true, "is_root": false,
      "static_lineage": true, "parent": "A", "children": [],
      "discoverable": ["A"],
      "properties": [
        { "id": "property_9f2k7d", "name": "Capacity", "type": "measurement",
          "data_element_id": "BATT-BATCH-CAP" }
      ]
    }
  ],
  "latest_version": 2,
  "has_draft": false,
  "metadata": {},
  "created_at": "2026-07-10T12:00:00Z",
  "updated_at": "2026-07-24T08:31:47Z"
}

PATCH/v1/ontologies/:id

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

PATCH
/v1/ontologies/ontology_00a1b2c3d4e5f607
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",
      "granularity": "model", "is_core": true, "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.

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 granularity cannot change or be removed — but a level that has none may be given one in a later version, which is how an ontology already in production starts reporting without being replaced;
  • a published is_core cannot be taken away: it is the level the registry was given as that tier's ID;
  • a published data_element_id cannot change or be withdrawn, since the registry may already hold values reported under it — but a property published without one may be mapped in a later version;
  • 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.

Within those rules, 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's levels. 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 id for a new property and one is minted; carry an existing id through verbatim to keep the values already stored against it.

Concurrency

Three guards, three different 409s:

CodeMeaningWhat to do
draft_existsPOST /draft when one is already openGET it, or DELETE it first
draft_conflictPATCH /draft with a stale base_updated_atRe-read the draft, reapply your changes
version_conflictpublish with a stale expected_latest_versionRe-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.

Ontology and version mutations are recorded on the change feed as ontology.created, ontology.updated and ontology.version.published. Opening, editing and discarding a draft record nothing.


GET/v1/ontologies/:id/versions

List versions

Every published version of the ontology, oldest first. Requires ontologies:read.

Each entry is a summary — its number, when it was published, and whether it is the version levels currently reflects. Read a version's schema with retrieve a version. Versions are immutable once published, so this list only ever grows, and it always comes back whole: has_more is false.

Request

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

Response

{
  "object": "list",
  "data": [
    {
      "object": "ontology.version",
      "version": 1,
      "published_at": "2026-07-10T12:00:00Z",
      "latest": false
    },
    {
      "object": "ontology.version",
      "version": 2,
      "published_at": "2026-07-24T08:31:47Z",
      "latest": true
    }
  ],
  "has_more": false,
  "next_cursor": null,
  "url": "/v1/ontologies/ontology_00a1b2c3d4e5f607/versions"
}

GET/v1/ontologies/:id/versions/:version

Retrieve a version

One published version, with the schema exactly as passports pinned to it read it. Requires ontologies:read. A number outside 1 to latest_version is 404.

This is how you resolve an older passport's schema: take its ontology_version and read that version's levels.

Request

GET
/v1/ontologies/ontology_00a1b2c3d4e5f607/versions/1
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",
      "granularity": null, "is_core": false, "is_root": true,
      "static_lineage": true, "parent": null, "children": ["B"],
      "discoverable": [], "properties": []
    },
    {
      "key": "B", "name": "Pack", "type": "series",
      "granularity": null, "is_core": false, "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.

  1. Open a draft — a copy of the latest published version.
  2. Edit it, sending the complete level map each time.
  3. Publish it as the next version, or delete it to throw the work away.

Deleting a draft never affects published versions.


GET/v1/ontologies/:id/draft

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

GET
/v1/ontologies/ontology_00a1b2c3d4e5f607/draft
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", "granularity": "model",
      "is_core": true, "is_root": true, "children": ["B"], "properties": [] },
    { "key": "B", "name": "Pack", "type": "series", "granularity": "batch",
      "is_core": true, "parent": "A", "children": [],
      "properties": [
        { "id": "property_9f2k7d", "name": "Capacity", "type": "measurement",
          "data_element_id": "BATT-BATCH-CAP" }
      ] }
  ]
}

POST/v1/ontologies/:id/draft

Open a draft

Copies the latest published version into a new draft and returns it with 201. 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

POST
/v1/ontologies/ontology_00a1b2c3d4e5f607/draft
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", "granularity": "model",
      "is_core": true, "is_root": true, "children": ["B"], "properties": [] },
    { "key": "B", "name": "Pack", "type": "series", "granularity": "batch",
      "is_core": true, "parent": "A", "children": [],
      "properties": [
        { "id": "property_9f2k7d", "name": "Capacity", "type": "measurement",
          "data_element_id": "BATT-BATCH-CAP" }
      ] }
  ]
}

PATCH/v1/ontologies/:id/draft

Edit the draft

Replaces the draft's structure. Requires ontologies:write.

Changes are validated against the published version on every save, together with the granularity and data element rules, so a violation surfaces here rather than at publish time. Whether every reported tier has its one reachable core is the exception: that is checked when you publish.

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. Each level carries name, type, is_root, children and properties, plus granularity and is_core when it reports. New levels may use any placeholder key — the server assigns the real one.

Request

PATCH
/v1/ontologies/ontology_00a1b2c3d4e5f607/draft
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,
            "granularity": "model", "is_core": true,
            "properties": [], "children": ["B", "new-module"]},
      "B": {"name": "Pack", "type": "series", "children": [],
            "granularity": "batch", "is_core": true,
            "properties": [
              {"id": "property_9f2k7d", "name": "Capacity", "type": "measurement",
               "data_element_id": "BATT-BATCH-CAP"}
            ]},
      "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", "granularity": "model",
      "is_core": true, "is_root": true, "children": ["B", "C"], "properties": [] },
    { "key": "B", "name": "Pack", "type": "series", "granularity": "batch",
      "is_core": true, "parent": "A", "children": [],
      "properties": [
        { "id": "property_9f2k7d", "name": "Capacity", "type": "measurement",
          "data_element_id": "BATT-BATCH-CAP" }
      ] },
    { "key": "C", "name": "Module", "type": "many", "granularity": null,
      "is_core": false, "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.


DELETE/v1/ontologies/:id/draft

Discard the draft

Throws the draft away. Requires ontologies:write. Published versions are never affected. 404 draft_missing when none is open.

Request

DELETE
/v1/ontologies/ontology_00a1b2c3d4e5f607/draft
curl -X DELETE https://api.synexcloud.com/v1/ontologies/ontology_00a1b2c3d4e5f607/draft \
  -H "Authorization: Bearer $SYNEX_API_KEY"

Response

{
  "object": "ontology.draft",
  "ontology": "ontology_00a1b2c3d4e5f607",
  "deleted": true
}

POST/v1/ontologies/:id/draft/publish

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 re-runs the additive-change checks and adds one of its own: every reported tier must have exactly one is_core level, placed where every path through its run passes through it. Until it does, publishing fails with 400 structure_invalid — it never picks a core for you.

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

POST
/v1/ontologies/ontology_00a1b2c3d4e5f607/draft/publish
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", "granularity": "model",
      "is_core": true, "is_root": true, "children": ["B", "C"], "properties": [] },
    { "key": "B", "name": "Pack", "type": "series", "granularity": "batch",
      "is_core": true, "parent": "A",
      "properties": [
        { "id": "property_9f2k7d", "name": "Capacity", "type": "measurement",
          "data_element_id": "BATT-BATCH-CAP" }
      ] },
    { "key": "C", "name": "Module", "type": "many", "granularity": null,
      "is_core": false, "parent": "A",
      "properties": [
        { "id": "property_4c8b1e", "name": "Slot", "type": "short_text" }
      ] }
  ]
}

Was this page helpful?