Generated banner for Ontology CLI
Generated logo for Ontology CLI

Ontology CLI

MicrosoftOfficialPublished by Microsoft

  • Skill
  • Tool integration
  • Data and AI
  • MIT
  • No issues found

From the author

Manages Microsoft Fabric Ontology items: authors legacy V1 entity/relationship definitions and answers questions for new-experience (V2) Ontologies through ask_ontology. Use for Ontology item operations or grounded questions; V2 authoring/upgrades and V1 queries are unsupported. Power BI data questions use fabriciq, report authoring powerbi-report-cli, and model/DAX changes semantic-model-authoring.
— the description by Microsoft

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The ontology-cli skill provides a specialized interface for interacting with Microsoft Fabric Ontology items. It operates in two distinct, non-convertible modes: an authoring mode for managing legacy V1 entity and relationship definitions, and a consumption mode for querying new-experience (V2) Ontologies. The skill acts as a mode dispatcher, requiring users to read specific reference documentation before executing commands to ensure correct payload generation and state management. It does not support V1-to-V2 upgrades or V2 authoring.

What it does

  • Creates and updates legacy V1 Ontology entity and relationship definitions.
  • Answers natural-language questions about existing new-experience (V2) Ontologies.
  • Generates grounded code or data insights from V2 Ontologies via the Ontology Agent.
  • Manages data bindings for V1 Ontology entity types.
  • Provides a structured workflow for resolving workspace and item identifiers.

When to use it

  • When you need to define or modify the structure of a legacy V1 Ontology.
  • When you need to query data or metadata from a new-experience (V2) Ontology.
  • When you need to generate runnable code based on V2 Ontology rules.
  • When you are working within the Microsoft Fabric ecosystem and require specific Ontology item operations.

Advantages

  • Ensures grounded, validated responses by using the Ontology Agent for V2 queries.
  • Provides a clear separation between legacy authoring and modern consumption workflows.
  • Reduces errors by enforcing mandatory reference reading before command execution.
  • Maintains consistency in Fabric item management through standardized CLI patterns.

Usage examples

“Using the ClinicOperations ontology, how many clinics are there?”
The agent enters consumption mode, verifies the ontology, and queries the data to return the clinic count.
“Create a legacy V1 ontology item with Customer and Order entity types bound to the lakehouse.”
The agent enters authoring mode, prepares the required JSON definition tree, and executes the creation call.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/microsoft/ontology-cli/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip ontology-cli.zip -d .agents/skills

Documentation by Microsoft

Telemetry — MANDATORY. Every api.fabric.microsoft.com call must carry x-ms-fabric-skill: ontology-cli (az rest: --headers "x-ms-fabric-skill=ontology-cli"), including every LRO poll, fabric_lro and retry. Snippets omit it — add it anyway.

CRITICAL NOTES

  1. To find the workspace details (including its ID) from workspace name: list all workspaces and, then, use JMESPath filtering

  2. To find the item details (including its ID) from workspace ID, item type, and item name: list all items of that type in that workspace and, then, use JMESPath filtering

  3. Skill disambiguation: use ontology-cli for operations on an Ontology item and questions grounded in that Ontology. Natural-language data questions over existing Power BI reports/models are fabriciq; report creation, editing, publishing, or rebinding is powerbi-report-cli; semantic-model or DAX authoring is semantic-model-authoring. Naming an Ontology as a report's data source does not move report authoring into this skill.

  4. Consumption runs through the Ontology Agent MCP server, never around it. Do not decode an ontology definition to hand-compose KQL / T-SQL / Spark SQL, and do not delegate ontology data questions to eventhouse-cli / sqldw-cli / spark-cli. That path loses the agent's grounding and validation and is not metered.

  5. Natural-language consumption supports only new-experience (V2) Ontologies. Call tools/list first. If ask_ontology is absent, stop and tell the user that V1 natural-language queries are unsupported and the Ontology must be upgraded or recreated in the new experience outside this skill's authoring flow. Never fall back to search_ontology.

  6. Authoring is legacy V1-only. The JSON EntityTypes/.../definition.json item-definition flow below cannot create, convert, or upgrade a new-experience (V2) Ontology. Do not route a V2 upgrade/recreation request into authoring.

  7. Kusto bindings use the parent Eventhouse item ID, never the KQL database ID. Validate the staged binding against discovered source metadata before presenting an authoring preview; unresolved or mismatched source identities block the write.

Ontology CLI Skill

This skill owns two explicit, non-convertible surfaces: legacy V1 JSON-definition authoring, and read-only questions over existing new-experience (V2) Ontologies through the Ontology Agent MCP endpoint. It does not upgrade V1 items to V2 or author V2 items.

It is a mode dispatcher and contains NO procedures. Pick the mode that matches the request from the table below, then read the matching references/<mode>.md file end to end with your file-reading tool BEFORE issuing a single command. That file holds the endpoints, payload shapes, templates and gotchas; acting without it produces wrong payloads and wrong results.

Mode selection

ModeUse when the request ...Example triggersRead this first
authoringcreates or updates a legacy V1 Ontology's JSON entity/relationship definitions or data bindings; V2 creation/upgrade is out of scopecreate legacy ontology item, bind V1 entity type, add V1 relationship type, update legacy ontology definitionreferences/authoring.md
consumptionasks a question about an existing new-experience (V2) Ontology -- its data, entities, or rules -- or wants grounded runnable code generated from ithow many clinics are there, which treatment rooms are busiest, what entities exist, list the ontology rules, generate KQL from this ontologyreferences/consumption.md

Consumption sub-references (read directly, not through chained links):

Mode boundary rule

consumption is read-only. Any definition write goes through the authoring mode's preview-and-confirm flow -- never call updateDefinition from a consumption request.

If a request genuinely spans modes, handle them one at a time and read each reference before you start that part. If the mode is ambiguous after reading this table, ask one short clarifying question instead of guessing.

Terminal write -- the step you must not skip

Reading the reference and planning the change is NOT completing the task. Each mutating mode ends with one state-changing call. If you did not issue it, nothing was persisted -- say so explicitly rather than reporting success.

ModeTerminal write
authoringcreateItem (POST /v1/workspaces/{ws}/items) to create a legacy V1 Ontology, then POST .../updateDefinition to persist entity types, relationships and bindings. This cannot create or upgrade V2.
consumptionnone -- this mode is read-only

Before you report the task done, confirm the terminal call returned success and, where the reference documents a readback, read the artefact back to prove the change landed.

Shared essentials (all modes)

Resolve the workspace and item first; every mode depends on it.

TaskReferenceNotes
Finding Workspaces and Items in FabricCOMMON-CLI.mdMandatory -- read before resolving any workspace or item id
Catalog SearchCOMMON-CORE.mdRequest/response shape, Catalog.Read.All scope, 24 h indexing delay
Pagination PatternCOMMON-CLI.mdCatalog Search and item listings both page via continuationToken
Fabric Topology & Key ConceptsCOMMON-CORE.mdItem types, workspaces, capacities
Environment URLsCOMMON-CORE.mdSovereign / non-public cloud hosts
Authentication & Token AcquisitionCOMMON-CORE.mdWrong audience = 401; read before any auth issue
Authentication RecipesCOMMON-CLI.mdaz login flows and token acquisition
Core Control-Plane REST APIsCOMMON-CORE.mdPagination, LRO polling, rate limiting
Gotchas & TroubleshootingCOMMON-CLI.mdaz rest audience, shell escaping, token expiry

Rules

MUST
  • Select exactly one mode from the table above before doing anything else.

  • Read references/<mode>.md end to end, as your FIRST tool call, before the first command of that mode. Read it ONCE, in a single full read: do not re-open it, do not grep it again, and do not page through it. You already have it.

  • Apply the same read-once discipline to every other file you load, including nested references/<mode>/*.md sub-references and shared common/*.md files: open only the ones you actually need, read each ONCE end to end, and never grep or re-open one you already loaded.

  • Resolve workspace and item ids by listing and filtering, never by guessing a GUID.

  • In consumption, call tools/list before the first tools/call. Continue only when ask_ontology is present; otherwise report that V1 natural-language consumption is unsupported and stop without calling another ontology tool.

  • Announce a mode switch explicitly when the request crosses a boundary.

  • Treat the reference as instructions, never as the deliverable. After reading it, RUN the documented commands against the live workspace and report the real results. Quoting what the reference says instead of executing it does not answer the request.

PREFER
  • The narrowest mode that satisfies the request.

  • Reading exactly ONE mode reference. Load a second only when the request genuinely spans modes, and say so before you do.

  • Reporting the mode you chose in your first response so the user can correct you.

AVOID
  • Acting from this dispatcher alone -- it intentionally omits the operational detail.

  • Answering with a summary of the reference instead of executing it.

  • Re-reading or re-grepping a reference you already loaded; it costs turns and tokens.

  • Mutating anything while in a read-only mode.

  • Answering an ontology data question with a hand-composed source query instead of the Ontology Agent MCP (see CRITICAL NOTE 4).

  • Loading a different skill for work this family already owns (see CRITICAL NOTES 3).

Examples

User requestModeReference to read
"Using the ClinicOperations ontology, how many clinics are there?"consumptionreferences/consumption.md
"Create a legacy V1 ontology item with Customer and Order entity types bound to the lakehouse."authoringreferences/authoring.md
"Upgrade this V1 ontology to the new V2 experience."out of scopeExplain that V2 upgrade/recreation is unavailable through this skill's authoring flow
"What entity types exist in the Sales ontology and how are they bound?"consumptionreferences/consumption.md
"Generate Eventhouse-runnable KQL for appointments per practitioner from this ontology."consumptionreferences/consumption.md

Files

references/authoring.md

<!-- Mode reference for the `ontology-cli` skill. Loaded on demand from `skills/ontology-cli/SKILL.md` when the request matches the `authoring` mode. -->

CRITICAL NOTES

  1. Ontology is preview. The item type value is Ontology. Features and wire format may change; validate against the current docs before production use.

  2. This authoring mode is legacy V1-only. Its EntityTypes/.../definition.json tree cannot create or upgrade the TMDL-backed new Ontology experience (V2). V2 creation, conversion, and upgrade must happen outside this skill.

  3. To find the workspace details (including its ID) from workspace name: list all workspaces and use JMESPath filtering.

  4. To find the item details (including its ID) from workspace ID, item type (Ontology), and item name: list all items of that type in that workspace and use JMESPath filtering.

  5. Authoring a relationship type requires two distinct entity types that already exist in the ontology. The source.entityTypeId and target.entityTypeId values are the entity type IDs you assigned, not item IDs.

  6. Data bindings reference a source table by workspaceId, itemId, sourceTableName, and — for lakehouse sources — sourceSchema. Lakehouse (LakehouseTable) sources carry the lakehouse item ID; Eventhouse (KustoTable) sources carry the Eventhouse item ID plus clusterUri and databaseName. Key column(s) on the source side must match the entity type's key property(ies). Eventhouse sources are TimeSeries-only; the static (NonTimeSeries) binding must come from a lakehouse.

ontology-cli authoring mode — Legacy V1 Ontology Authoring via CLI

This mode creates and mutates the legacy V1 JSON definition tree. It does not author, recreate, convert, or upgrade a new-experience (V2) Ontology. If the user asks for V2 creation or migration, state that the operation is outside this CLI authoring flow and stop before any write.

Table of Contents

TaskReferenceNotes
Finding Workspaces and Items in FabricCOMMON-CLI.md § Finding Workspaces and Items in FabricMandatory — resolve workspace/item IDs before authoring
Fabric Topology & Key ConceptsCOMMON-CORE.md § Fabric Topology & Key ConceptsWorkspace → Item hierarchy
Authentication & Token AcquisitionCOMMON-CORE.md § Authentication & Token AcquisitionUse https://api.fabric.microsoft.com audience for control plane
Core Control-Plane REST APIsCOMMON-CORE.md § Core Control-Plane REST APIsCreate Item, Get/Update Item Definition
Long-Running Operations (LRO)COMMON-CORE.md § Long-Running Operations (LRO)Item create/update returns an LRO
Rate Limiting & ThrottlingCOMMON-CORE.md § Rate Limiting & Throttling
Authentication RecipesCOMMON-CLI.md § Authentication Recipesaz login; token acquisition
Fabric Control-Plane API via az restCOMMON-CLI.md § Fabric Control-Plane API via az restAlways pass --resource https://api.fabric.microsoft.com
Long-Running Operations (LRO) PatternCOMMON-CLI.md § Long-Running Operations (LRO) PatternFor Ontology create/update, poll /v1/operations/{x-ms-operation-id} on the Fabric host — see LRO Header Capture
Gotchas & Troubleshooting (CLI-Specific)COMMON-CLI.md § Gotchas & Troubleshooting (CLI-Specific)Token audience, shell escaping
az rest TemplateCOMMON-CLI.md § az rest Template
Definition Envelope (parts, payloadType)ITEM-DEFINITIONS-CORE.md § Definition EnvelopeInlineBase64 parts pattern used for Ontology
Ontology Definition ReferenceONTOLOGY-AUTHORING-CORE.md § Definition TreeAuthoritative file/folder layout for the ontology item
EntityType & EntityTypeProperty schemaONTOLOGY-AUTHORING-CORE.md § EntityType fileAllowed valueType values, key constraints, name regex
DataBinding schema + source-type mappingONTOLOGY-AUTHORING-CORE.md § DataBinding fileLakehouse & Eventhouse shapes; value-type mapping; binding rules
RelationshipType + Contextualization schemaONTOLOGY-AUTHORING-CORE.md § RelationshipType fileSource/target constraints, link table requirements
Ontology ConceptsOntology Item ConceptsEntity types, properties, bindings, relationship types
Tool StackTool Stack
ConnectionConnectionDiscover workspace, lakehouse, ontology IDs
Authoring ScopeAuthoring ScopeSupported operations at a glance
Authoring Mechanics (full reference)authoring-mechanics.mdEnvelope, IDs, create, entity types, bindings, relationships, update, verify
Worked Examplesexamples.mdEnd-to-end bash recipes (create → bind → relationship → timeseries)
Preview & Confirm (mandatory before LRO write)preview-and-confirm.mdASCII proposal (greenfield) / change-set diff (brownfield)
Script Templatesdefinition-script-templates.mdBash / PowerShell fetch-mutate-send scaffolds
Must / Prefer / Avoid / TroubleshootingMust / Prefer / Avoid / TroubleshootingLLM decision rules
Agentic WorkflowsAgentic WorkflowsExploration-before-authoring, script generation
Agent Integration NotesAgent Integration NotesHow this skill composes with agents / other skills

Ontology Item Concepts

A Fabric Ontology item is authored as a tree of JSON files inside the item definition. Each file is carried as a part in the parts[] array of the Create/Update definition envelope (payloadType InlineBase64).

ConceptDefinition file pathPurpose
Ontology envelopedefinition.jsonEmpty {}; required
Platform metadata.platform{ "metadata": { "type": "Ontology", "displayName": "<name>" } }
Entity typeEntityTypes/{entityTypeId}/definition.jsonName, namespace, key(s), display name property, properties[], timeseriesProperties[]
Entity type data bindingEntityTypes/{entityTypeId}/DataBindings/{guid}.jsonMaps a lakehouse or eventhouse table to properties; dataBindingType = NonTimeSeries or TimeSeries. Eventhouse (KustoTable) sources are allowed only for TimeSeries
Entity type documentsEntityTypes/{entityTypeId}/Documents/{name}.jsonOptional doc links
Entity type overviewsEntityTypes/{entityTypeId}/Overviews/definition.jsonOptional widgets layout
Entity type resource linksEntityTypes/{entityTypeId}/ResourceLinks/definition.jsonOptional Power BI / item links
Relationship typeRelationshipTypes/{relTypeId}/definition.jsonSource + target entity type IDs, name
Relationship contextualizationRelationshipTypes/{relTypeId}/Contextualizations/{guid}.jsonSource/target key bindings onto a lakehouse table

Property valueType allowed values (exact): String, Boolean, DateTime, Object, BigInt, Double. Use BigInt — not Int64 — for integers; there is no Guid value type (model GUIDs as String). Timeseries bindings require a timestamp column (source type datetime / date / timestamp) and a TimeSeries binding with timestampColumnName. See ONTOLOGY-AUTHORING-CORE.md § EntityTypeProperty for the full source-column → valueType mapping.

⚠️ Property names must be unique across both properties[] and timeseriesProperties[] within a single entity type. If a lakehouse table and an Eventhouse table both contain a column with the same name (e.g., tenant_id), you must rename one of the ontology property names to avoid a collision. The sourceColumnName in the binding can still point to the original column — only the ontology property name must be unique. For example, keep the static property as TenantId and name the timeseries one TsTenantId.

⚠️ Property names with the same name across different entity types must share the same valueType — the ontology enforces name-level type consistency across the entire definition. If SerialNum is String on one entity type, it cannot be BigInt on another. Either use the same valueType everywhere, or disambiguate with a prefix (e.g., SerialNumStr vs SerialNumInt).

⚠️ Part paths must always use forward slashes (EntityTypes/{id}/definition.json), never backslashes. On Windows, PowerShell path-joining operators (Join-Path, \) produce backslashes that the Fabric API rejects with ALMOperationBadRequest. Always build part paths with string interpolation using /.


Tool Stack

Ontology authoring uses the same Fabric control-plane tool stack as every other CLI skill — see COMMON-CLI.md § Tool Selection Rationale for the canonical list (install commands, prerequisite checks, base64 helpers, JSON tooling) and COMMON-CLI.md § Authentication Recipes for az login + token acquisition.

Ontology-specific tool guidance below covers only the gotchas that hit createItem / updateDefinition payloads.

⚠️ PowerShell ConvertTo-Json Warning: PowerShell's ConvertTo-Json can silently reorder keys and serialize $null differently than JSON null, which can cause ALMOperationImportFailed errors on updateDefinition. To avoid this:

  1. Always use [System.IO.File]::WriteAllText with [System.Text.UTF8Encoding]::new($false) to write JSON files — Out-File and Set-Content add a BOM that corrupts the payload.

  2. Build JSON with jq instead of ConvertTo-Json where possible — jq -nc produces deterministic, compact JSON without PowerShell serialization quirks:

    $json = '{}' | jq -nc --arg id "$ET_ID" --arg name "Site" '{id:$id,name:$name}'
  3. Validate the JSON before sending: Get-Content envelope.json | jq . — if jq fails, the payload is malformed.

  4. Use -Depth 10 on ConvertTo-Json — the default depth of 2 silently truncates nested objects.

⚠️ Avoid certutil -encode for InlineBase64 parts. Its output is line-wrapped with a header/footer and must be post-processed before use. On Windows, use PowerShell's [Convert]::ToBase64String([IO.File]::ReadAllBytes($path)) instead.


Connection

Ontology authoring targets the Fabric control plane. Before composing the definition you need: WS_ID (workspace), LH_ID (lakehouse item ID for static + lakehouse-timeseries bindings), and — for Eventhouse-backed timeseries — the Eventhouse item ID, KQL cluster URI, and KQL database name.

  • Sign in + acquire the Fabric control-plane token → COMMON-CLI.md § Authentication Recipes (always --resource https://api.fabric.microsoft.com).

  • Resolve workspace, folder, lakehouse, and ontology item IDs by displayName → COMMON-CLI.md § Finding Workspaces and Items in Fabric (covers pagination + JMESPath filtering).

  • Generic az rest invocation template → COMMON-CLI.md § Fabric Control-Plane API via az rest.

The Ontology-specific resolution gotchas (folder GUID vs. portal numeric ID, Eventhouse ID field mapping, schema discovery for bindings, LRO header capture on the preview redirect host) follow.

Folder (Ontology-specific gotcha)

The folderId field on the Ontology create payload requires the folder GUID — not the numeric subfolderId shown in portal URLs. Passing the portal number fails with 400 InvalidParameter … cannot convert "<numeric>" to Guid … Path 'folderId'. Resolve the GUID by listing GET /v1/workspaces/{WS_ID}/folders (per COMMON-CLI.md § Finding Workspaces and Items in Fabric) and filter by displayName.

Eventhouse / KQL Database (for TimeSeries bindings)

Ontology TimeSeries bindings backed by Eventhouse require three fields in the KustoTable data-binding payload — sourced from the KQL database record returned by GET /v1/workspaces/{WS_ID}/kqlDatabases:

KustoTable binding fieldSource field on the KQL-database record
itemIdproperties.parentEventhouseItemId — the Eventhouse item ID, not the KQL database's own id
clusterUriproperties.queryServiceUri
databaseNamedisplayName (use the canonical casing returned by the API)

Eventhouse tables can back TimeSeries bindings only. The entity type's static (NonTimeSeries) binding must still come from a managed lakehouse table.

Binding identity preflight

Keep KQL_DATABASE_ID (the record's id) and EVENTHOUSE_ID (properties.parentEventhouseItemId) as separate values. Before presenting a preview, decode each staged KustoTable binding and compare its itemId with EVENTHOUSE_ID from the discovered database record. It must equal that parent ID, not KQL_DATABASE_ID; also compare clusterUri and databaseName with the same record. Include both source IDs in the preview so the distinction is reviewable.

If the parent ID is absent, retrieve the KQL database details before composing the binding. If it remains unresolved or any comparison fails, stop before confirmation or any write. Never substitute the database ID, and never repair an already-confirmed payload silently; corrected bindings need a new preview.

For the generic kqlDatabases listing call (pagination + JMESPath filter by displayName), see COMMON-CLI.md § Finding Workspaces and Items in Fabric.

Schema Discovery

Before composing bindings, discover the source table schemas so you map the correct column names. Use companion skills for schema discovery — they are faster and more reliable than raw REST calls.

  • Lakehouse tables → route to the sqldw-cli skill and query INFORMATION_SCHEMA.COLUMNS against the lakehouse SQL endpoint (returns all tables + columns in one query). If unavailable, fall back to the Fabric Tables REST API plus the OneLake Table API for Iceberg metadata.

  • Eventhouse / KQL tables → route to the eventhouse-cli skill and run .show database schema as json (returns every table + column in a single response). For a single table, use .show table <name> schema as json.

Use the column types returned here to fill valueType on each EntityTypeProperty — see the source-column → valueType mapping in ONTOLOGY-AUTHORING-CORE.md § EntityTypeProperty.

LRO Header Capture with az rest

az rest does not expose response headers by default. Both createItem and updateDefinition return 202 Accepted with an x-ms-operation-id header (and a Location header). Use --verbose and parse stderr to capture the operation id:

Prefer polling with x-ms-operation-id over the raw Location header for Ontology create/update. The public Fabric LRO contract supports polling either the Location header or https://api.fabric.microsoft.com/v1/operations/{operationId} (from x-ms-operation-id). In current Ontology preview behavior, observed Location values point at an analysis.windows.net redirect host (e.g. https://df-…-redirect.analysis.windows.net/v1/operations/{id}), not api.fabric.microsoft.com; polling that URL with az rest --resource https://api.fabric.microsoft.com re-authenticates against the wrong audience and fails with 401/403 — which hides the LRO status/error and leads to blind-retry loops. So capture x-ms-operation-id and poll https://api.fabric.microsoft.com/v1/operations/{operationId} (Fabric host, Fabric token). If you do follow Location, use the audience that URL requires.

# Bash — capture x-ms-operation-id from az rest --verbose stderr
OP_ID=$(az rest --method POST \
  --url "https://api.fabric.microsoft.com/v1/workspaces/${WS_ID}/items" \
  --resource "https://api.fabric.microsoft.com" \
  --headers "Content-Type=application/json" \
  --body @envelope.json --verbose 2>&1 \
  | grep -oiP "(?<=x-ms-operation-id': ')[^']+")

# Poll the Fabric operations endpoint (stays on api.fabric.microsoft.com)
while :; do
  OP=$(az rest --method GET \
    --url "https://api.fabric.microsoft.com/v1/operations/${OP_ID}" \
    --resource "https://api.fabric.microsoft.com")
  STATUS=$(printf '%s' "$OP" | jq -r .status)
  case "$STATUS" in
    Succeeded) break ;;
    Failed|Cancelled)
      # Read the error and FIX the payload — never blind-retry the same body.
      printf '%s' "$OP" | jq -r '.error | "\(.errorCode // .code): \(.message)"' >&2
      exit 1 ;;
    *) sleep 5 ;;
  esac
done
# PowerShell — capture x-ms-operation-id from az rest --verbose stderr
$payloadPath = (Resolve-Path -LiteralPath 'envelope.json' -ErrorAction Stop).Path
$null = Get-Content -LiteralPath $payloadPath -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
$result = az rest --method POST `
  --resource "https://api.fabric.microsoft.com" `
  --url "https://api.fabric.microsoft.com/v1/workspaces/$WS_ID/items" `
  --headers "Content-Type=application/json" `
  --body "@$payloadPath" --verbose 2>&1
$opId = ($result | Select-String -Pattern "x-ms-operation-id': '([^']+)'" |
  ForEach-Object { $_.Matches[0].Groups[1].Value })

# Poll the Fabric operations endpoint (stays on api.fabric.microsoft.com)
do {
  Start-Sleep -Seconds 5
  $op = az rest --method GET `
    --url "https://api.fabric.microsoft.com/v1/operations/$opId" `
    --resource "https://api.fabric.microsoft.com" | ConvertFrom-Json
} while ($op.status -notin 'Succeeded','Failed','Cancelled')
if ($op.status -ne 'Succeeded') {
  # Read .error and FIX the payload — never blind-retry the same body.
  throw "createItem LRO $($op.status): $($op.error.errorCode) $($op.error.message)"
}

Important: createItem returns 202 with no response body — az rest exits with code 0 and prints nothing. This is normal. After the operation reaches Succeeded, list items to capture the new item ID. If status is Failed, the error field names the cause (e.g. a malformed entity-type part) — fix the payload and submit a corrected request rather than re-sending the same body.

One confirmation authorizes one write attempt, including an attempt that returns HTTP 400. Capture the complete verbose output from that call and inspect the captured variable for status and headers. If the operation ID is not parsed, do not repeat createItem or updateDefinition just to expose headers; inspect the original output, list/read back the target item, and report an ambiguous result if success still cannot be established. A retry requires a corrected preview and fresh confirmation, even when only the shell transport needs correction.

PowerShell request files and readback

Before preview, stage and locally validate the complete UTF-8-without-BOM envelope at an absolute path. After yes, submit the unchanged file once with quoted --body "@$payloadPath". Never use inline JSON or --body $body from Get-Content: Windows native argument passing can strip JSON quotes. Never rewrite or reserialize the confirmed file. Any HTTP error consumes that attempt; correcting the transport or payload requires a new preview and confirmation.

Save getDefinition to a separate response file and decode in the current shell:

$d = Get-Content -LiteralPath '.\readback.json' -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
$part = $d.definition.parts | Where-Object path -eq 'definition.json'
[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($part.payload))

Do not nest pwsh -Command for readback or diagnostics: outer string expansion can consume child variables. Never overwrite the confirmed file with readback.


Authoring Scope

Every operation in this table targets legacy V1 item definitions only.

OperationFabric REST CallDefinition Parts Touched
Create empty ontologyPOST /v1/workspaces/{ws}/items with type=Ontology.platform, definition.json
Add / alter entity typePOST /v1/workspaces/{ws}/items/{id}/updateDefinitionEntityTypes/{id}/definition.json
Bind entity type to table (non-timeseries)updateDefinitionEntityTypes/{id}/DataBindings/{guid}.json with NonTimeSeries
Bind entity type to table (timeseries)updateDefinitionEntityTypes/{id}/DataBindings/{guid}.json with TimeSeries + timestampColumnName
Add relationship typeupdateDefinitionRelationshipTypes/{id}/definition.json
Bind relationship (contextualization)updateDefinitionRelationshipTypes/{id}/Contextualizations/{guid}.json
Delete entity / relationshipupdateDefinition with that path omitted from parts—
Rename ontologyupdateDefinition with updateMetadata=true and new .platform.platform

Update Item Definition replaces the full tree of included parts. Always fetch the current definition via Get Item Definition, mutate the parts locally, and resend the complete desired set.


Authoring Reference

Full JSON shapes, field contracts, and verification recipes for each operation live in authoring-mechanics.md. Worked end-to-end bash recipes live in examples.md. Use the sections below as a quick index:

TopicReference
Definition envelope (parts[], InlineBase64, base64 helpers)authoring-mechanics.md § Definition Envelope
ID generation (64-bit ints, GUIDs, name → id map)authoring-mechanics.md § ID Generation Pattern
Create empty ontologyauthoring-mechanics.md § Create the Ontology Item
Add an entity typeauthoring-mechanics.md § Add an Entity Type
Bind to lakehouse / eventhouseauthoring-mechanics.md § Bind an Entity Type
Relationship types + contextualizationsauthoring-mechanics.md § Add a Relationship Type
Apply a definition update (fetch → mutate → send)authoring-mechanics.md § Apply a Definition Update
Verify and inspectauthoring-mechanics.md § Verify and Inspect
Complete Bash / PowerShell scaffoldsdefinition-script-templates.md

Core invariants to keep in mind when authoring (full detail in the reference files):

  • Envelope shape: { "displayName", "type": "Ontology", "definition": { "parts": [ { "path", "payload", "payloadType": "InlineBase64" } ] } }; definition.json is literally {}; .platform carries metadata.type: "Ontology" + displayName.

  • IDs: entity / relationship / property IDs are positive 64-bit integers, data binding / contextualization IDs are GUIDs. Persist the name → id map in source control; never reuse an ID for a different concept.

ID map template — persist this alongside your deployment scripts (JSON or YAML):

{
  "ontologyName": "SkillTest_Fleet",
  "entityTypes": {
    "Site":      { "id": "1048860412765431174", "properties": { "SiteId": "1428056703884423742", "SiteName": "4251708967918658190" } },
    "Equipment": { "id": "3332700945676096991", "properties": { "EquipmentId": "4585483423451989345" } }
  },
  "relationshipTypes": {
    "EquipmentAtSite": { "id": "4242053467032157032" }
  },
  "bindings": {
    "Site_static":      "25e3a44a-b62a-40e3-a64a-a43caaa92d19",
    "Equipment_static": "5dc4cadd-3700-4c96-bb1e-41e4c909ae4d"
  }
}
  • Bindings: NonTimeSeries is lakehouse-only and at most one per entity type; a NonTimeSeries binding is required before any TimeSeries binding on the same entity type; TimeSeries can be lakehouse or Eventhouse; for KustoTable, itemId is the Eventhouse item ID (not the KQL database ID).

  • Relationships: source.entityTypeId and target.entityTypeId must be distinct and must reference entity types present in the parts tree.

  • Updates replace the included parts wholesale — always fetch the current definition, mutate locally, then send.


Must / Prefer / Avoid / Troubleshooting

Must
  • Reject V2 creation, conversion, and upgrade requests — this authoring surface emits the legacy V1 JSON definition tree and cannot produce the TMDL-backed new experience. Do not create another V1 item when the user needs V2.

  • Require explicit ontology context before routing here — the prompt must ask to create or change an "ontology" (or reference an ontology item). Generic "Fabric IQ" prompts without ontology context are not ontology-authoring tasks; defer them to the matching skill. This keeps the shared "Fabric IQ" brand from over-triggering this skill.

  • Clarify before acting on ambiguous prompts — never infer schema or bindings. If the user says "create an ontology for airline data" without naming entity types, their keys, or the lakehouse tables, ask what entities, what keys, and which lakehouse tables. Irreversible side-effects (replacing an ontology definition) require explicit user intent.

  • Resolve WS_ID and source item IDs before composing any binding — hardcoded GUIDs are a top-3 failure mode. Lakehouse bindings need the lakehouse itemId; eventhouse bindings need the eventhouse itemId, cluster URI, and database name.

  • Fetch the current definition before any update — updateDefinition replaces included parts wholesale. Merging with stale local state silently drops recent changes. Handle the LRO 202 on getDefinition (poll and retrieve via the operation's result endpoint).

  • Persist the name → id map for entity types, relationship types, and properties in source control alongside the skill consumer's repo. Regenerating IDs on every run creates duplicates and breaks references.

  • Add the static (NonTimeSeries) binding before any timeseries binding on an entity type — each entity type supports at most one static binding, and timeseries binding requires the static key property to already be populated.

  • Bind only to managed lakehouse tables — external tables, lakehouses with OneLake security enabled, and delta tables with column mapping enabled are not supported.

  • Ensure property names are unique across properties[] and timeseriesProperties[] within each entity type. When a lakehouse table and an Eventhouse table share a column name (e.g., tenant_id, device_id), rename the ontology timeseries property (e.g., TsTenantId) while keeping sourceColumnName pointing at the original column. Duplicate property names cause ALMOperationImportFailed.

  • Observed (preview): restrict entity keys (entityIdParts) to properties whose valueType is String or BigInt — other value types have not been accepted as keys in current preview behavior. This restriction is not documented on public Microsoft Learn; verify against your tenant before relying on it.

  • Use forward slashes in all part paths — EntityTypes/{id}/definition.json, never EntityTypes\{id}\definition.json. On Windows, Join-Path and \ produce backslashes that the Fabric API rejects. Build paths with string interpolation: "EntityTypes/$ET_ID/definition.json".

  • Verify permissions — authoring requires at least Contributor on the workspace.

  • Treat the item type as Ontology (not OntologyPreview or similar) in both the envelope's type and the .platform metadata.

  • Render a Preview & Confirm gate before every LRO write — include the ASCII proposal (greenfield) or change-set diff vs. getDefinition (brownfield) in the final assistant message, then obtain explicit yes before calling createItem or updateDefinition. Tool output alone is not the preview. See preview-and-confirm.md. Anything other than yes means stop and revise; never partially apply.

  • Use the confirmed request file once and verify in the same PowerShell process — follow PowerShell request files and readback; an HTTP failure still consumes that write authorization.

Prefer
  • Building the definition tree on disk (one JSON per logical part) and base64-encoding it into the staged envelope before preview. This keeps diffs reviewable without rebuilding the confirmed request.

  • Starting from a getDefinition dump of an existing known-good ontology when onboarding, then mutating.

  • Lakehouse-first static binding; Eventhouse for time-series — OneLake (lakehouse) is the only supported source for NonTimeSeries bindings. Use Eventhouse (KustoTable) for high-volume telemetry on TimeSeries bindings, or mirror/shortcut the data into a lakehouse table if the team prefers a single source kind.

  • Idempotent deploy scripts — re-running the script with unchanged inputs should produce an unchanged ontology.

  • Scripted workflow over UI when more than one entity type or environment is involved.

  • Triggering a manual graph-model refresh after upstream data writes — new rows in bound sources are not visible in the preview experience until the ontology is refreshed.

Avoid
  • Generating monolithic .ps1 or .sh script files — execute commands directly in the shell. Large generated scripts introduce escaping bugs, PowerShell parse errors, and are hard to debug when a single line fails. Build JSON with jq -nc, write to a temp file, and pass to az rest --body @file.

  • Hand-editing base64 payloads — always decode, edit the JSON, then re-encode.

  • Reusing a property/entity/relationship ID for a different concept.

  • Relying on relationship-name uniqueness outside the ontology scope — today, relationship names appear to be unique within an ontology (observed behavior); collect the full set of desired relationship names up front so you can disambiguate with prefixes if needed. Confirm naming collisions with the consumer rather than guessing.

  • Embedding secrets, SAS tokens, or user tokens in .platform or any part.

  • Creating relationship types before their source and target entity types exist in the parts list.

  • Treating Object / JSON properties as fully queryable — observed behavior today is that nested JSON bound to an Object property surfaces as an opaque payload rather than being addressable like a scalar. For nested payloads, keep the raw data in Eventhouse and bind only the addressable scalar fields. Verify with a getDefinition round-trip before promising downstream consumers a specific query shape.

  • Relying on "delete by omission" — parts not included in the updateDefinition body are observed to be removed, but the skill should tell the user this is destructive and confirm before generating an envelope that drops parts.

Troubleshooting
SymptomLikely CauseFix
400 Bad Request on create — "Invalid item type"Wrong item type stringUse "type": "Ontology" and metadata.type: Ontology in .platform
400 InvalidItemType on createItem with type: OntologyTenant / workspace does not have Fabric IQ Ontology (preview) enabledSurface to the user — do not retry. Ontology preview enrollment is required at the tenant level.
400 InvalidParameter on create — Error converting value "<number>" to … Guid … Path 'folderId'Passed the numeric subfolderId from a portal URL instead of the folder GUIDResolve the folder GUID via GET /v1/workspaces/{WS_ID}/folders (see Connection § Folder) and pass that
400 — "Invalid value type" on propertyUsing Int64 / Guid / Float as valueTypeAllowed values are exactly String, Boolean, DateTime, Object, BigInt, Double
400 — "Invalid identifier" on entity type / propertyName violates regexMatch ^[a-zA-Z][a-zA-Z0-9_-]{0,127}$; prefer the stricter 1–26 char portal rule to stay portable
400 — "Source and target must differ"Relationship points at the same entity type twiceChoose distinct source/target entity types
400 — "Referenced property not found"targetPropertyId doesn't match any property in the entity typeCheck IDs; ensure property was added in the same update
400 — "Time series binding requires existing static binding"Timeseries binding added before the static binding on an entity typeAdd a NonTimeSeries binding with the key property first, then the TimeSeries binding
400 — key column issueKey property valueType is not String or BigIntChange the property to String / BigInt, or choose a different key
404 on bindingworkspaceId / itemId wrong, or source item deletedRe-resolve IDs via list items / list lakehouses / list eventhouses
Binding accepted but no instances appearSource is external table, column-mapped delta, or OneLake-securedRebuild the table as a managed delta table without column mapping; remove OneLake security on the lakehouse
Instances empty after bindingpropertyBindings column names don't match source columnsInspect the source schema and fix sourceColumnName / sourceSchema
New upstream rows not appearingNo refresh performedTrigger a manual graph-model refresh on the ontology item
Timeseries widget shows no datatimestampColumnName not set, or timestamp column is not a supported date/time typeSet timestampColumnName in the TimeSeries binding; ensure column type is datetime / date / timestamp
getDefinition returns 200 or 202LRO-capable response (may be inline envelope or operation-id)If 202, poll the operation until Succeeded, then GET https://api.fabric.microsoft.com/v1/operations/{operationId}/result; if 200, parse the returned envelope directly — see LRO Header Capture
LRO poll returns 401/403, or the Location header host is *.analysis.windows.netThe create/update Location redirects to an Analysis Services host; polling it with az rest --resource https://api.fabric.microsoft.com re-auths against the wrong audiencePoll https://api.fabric.microsoft.com/v1/operations/{x-ms-operation-id} on the Fabric host instead of following the Location URL — see LRO Header Capture. Do not blind-retry the create while the poll is failing
Conflict on updateDefinitionConcurrent edit from the portalRe-fetch definition, re-apply mutations, show the revised preview, and obtain fresh confirmation before resending
ALMOperationImportFailed on updateDefinitionMalformed JSON payload — often caused by PowerShell ConvertTo-Json serialization quirks ($null vs null, key reordering, BOM in file)Build JSON with jq -nc instead of ConvertTo-Json; write files with [System.IO.File]::WriteAllText + UTF8Encoding($false) to avoid BOM; validate with jq . before sending — see Tool Stack § PowerShell Warning
ALMOperationImportFailed on createItem or updateDefinition — duplicate property nameA property name appears in both properties[] and timeseriesProperties[] on the same entity typeProperty names must be unique across both arrays. If a lakehouse and Eventhouse table share a column name, rename the timeseries ontology property (e.g., TenantId → TsTenantId) — the binding's sourceColumnName can still reference the original column
ALMOperationImportFailed — "Property 'X' has conflicting value types"The same property name appears on two different entity types with different valueType values (e.g., String on one, BigInt on another)Property names are unique across the entire ontology — if two entity types share a property name, both must use the same valueType. Disambiguate with a prefix (e.g., SerialNumStr vs SerialNumInt) or unify the type
ALMOperationBadRequest — "directory name … is not valid for EntityType"Part path uses backslashes (EntityTypes\\{id}\\definition.json) instead of forward slashesAlways use forward slashes in part paths: EntityTypes/{id}/definition.json. On Windows, avoid Join-Path or \ for part paths — use string interpolation with /
createItem returns exit code 0 but no outputNormal — createItem returns 202 Accepted with no body; az rest treats this as successList items after the LRO completes to capture the new item ID; use --verbose to capture the x-ms-operation-id header for LRO polling
409 ItemDisplayNameAlreadyInUse on createItemOntology with the same displayName already exists in the workspaceList existing ontologies first; delete or rename the existing one, or choose a different name
definition.json payload causes import errorExtra whitespace, BOM, or newlines in the base64 payloaddefinition.json must be exactly {} — its base64 is e30=. On Windows, ensure no BOM by using [System.IO.File]::WriteAllText with UTF8Encoding($false)

Agentic Workflows

⚠️ Do NOT generate monolithic .ps1 / .sh script files. Execute each step directly in the shell as individual commands. Generating a large script file introduces escaping bugs, parse errors, and property-access issues that are hard to debug. Instead:

  • Run az rest, jq, and PowerShell commands directly in the terminal

  • Build JSON payloads incrementally using jq -nc piped through variables

  • Write the final envelope to a temp file, then pass it to az rest --body @file

  • If a step fails, diagnose it without regenerating the entire script. Local preparation and reads may be rerun; another write requires a corrected preview and fresh confirmation.

Exploration Before Authoring

Greenfield vs brownfield execution strategy:

  • Greenfield (new ontology): Build the complete definition — entity types, bindings, relationships, contextualizations, timeseries — as a single createItem call with all parts in one envelope. This is faster and avoids intermediate states. The createItem payload accepts the full definition.parts[] array, not just .platform + definition.json.

  • Brownfield (updating existing): Execute incrementally — fetch the current definition, mutate, send. Verify with getDefinition after each updateDefinition to catch errors early. A failure partway through preserves prior progress.

Parallel Schema Discovery

When the ontology binds to multiple data sources (lakehouse tables + Eventhouse tables), discover schemas in parallel rather than sequentially. Launch separate discovery tasks that run concurrently:

┌─────────────────────────────────────────────────────────────────┐
│  ORCHESTRATOR (this skill)                                       │
│                                                                   │
│  Step 0 → Resolve workspace, folder, lakehouse ID, eventhouse ID │
│                                                                   │
│  Step 1 → Fan out schema discovery (parallel):                   │
│     ┌──────────────────────────┐  ┌────────────────────────────┐ │
│     │ TASK A: Lakehouse schemas │  │ TASK B: Eventhouse schemas │ │
│     │ sqldw-cli     │  │ eventhouse-cli │ │
│     │ or INFORMATION_SCHEMA     │  │ or .show database schema   │ │
│     │ → all tables + columns    │  │ → all tables + columns     │ │
│     └──────────┬───────────────┘  └──────────┬─────────────────┘ │
│                │                              │                   │
│  Step 2 → Merge schemas ◄────────────────────┘                   │
│     - Match entity tables (lakehouse) to telemetry (eventhouse)  │
│     - Detect property name collisions across sources             │
│     - Detect property type conflicts across entity types         │
│     - Rename collisions (e.g., TenantId → TsTenantId)           │
│                                                                   │
│  Step 3 → Stage full envelope → PREVIEW & CONFIRM               │
│                                                                   │
│  Step 4 → Submit unchanged envelope → createItem (single call)  │
└─────────────────────────────────────────────────────────────────┘

How to fan out (agent-specific):

  • GitHub Copilot CLI / Claude Code: launch two background task agents — one for lakehouse (sqldw-cli or INFORMATION_SCHEMA.COLUMNS query), one for Eventhouse (.show database schema as json). Read both results when they complete.

  • Single-threaded environments: run the two discovery queries sequentially — each is a single call, so the overhead is minimal.

The merge step (Step 2) is where most authoring bugs are caught — deduplicate property names, unify valueType across entities, and prefix timeseries properties that collide with static ones.

Detailed Step Flow
Step 0 → Is the request specific? Are the entity types, keys, and lakehouse tables named?
         → NO  → Ask: "Which entity types? What is the key of each? Which lakehouse table binds to each?
                  Any timeseries properties? Any relationships and their link tables?"
                  STOP — do not proceed until the user answers.
         → YES → Continue.
Step 1 → Resolve IDs: workspace, folder, lakehouse, eventhouse     [COMMON-CLI.md]
Step 2 → Discover source schemas (parallel where possible):
           a. Lakehouse: invoke `sqldw-cli` or query INFORMATION_SCHEMA.COLUMNS
           b. Eventhouse: invoke `eventhouse-cli` or run `.show database schema as json`
Step 3 → Merge schemas: detect property name collisions + type conflicts; rename as needed
Step 4 → If ontology exists: getDefinition → decode parts              (capture current IDs)
         Else: plan `createItem` with the FULL definition (all parts in one call).
Step 5 → For each entity type:
           a. Generate/reuse 64-bit IDs for entity + properties
           b. Build EntityTypes/{id}/definition.json
           c. Build one or two DataBindings/{guid}.json files
Step 6 → For each relationship:
           a. Confirm both entity types exist in Step 5 output
           b. Generate/reuse relationship type ID
           c. Build RelationshipTypes/{id}/definition.json
           d. Build RelationshipTypes/{id}/Contextualizations/{guid}.json
Step 7 → Base64-encode all parts; stage and locally validate the complete envelope file
Step 8 → **PREVIEW & CONFIRM** — render proposal (greenfield) or change-set diff (brownfield)
         and obtain explicit `yes` from the user. See [preview-and-confirm.md](authoring/preview-and-confirm.md).
         Do not proceed on anything other than `yes`.
Step 9 → Submit the unchanged confirmed file once: createItem OR updateDefinition (LRO)
Step 10 → Poll LRO until Succeeded; getDefinition; decode in the current shell; verify IDs and bindings; persist a separate post-write snapshot
Script Generation Workflow
Step 1 → Capture user intent (entity types, keys, properties, relationships, source tables)
Step 2 → Save intent as a YAML/JSON spec in the consumer's repo — single source of truth
Step 3 → Generate: (a) the ID map, (b) per-file JSON parts, (c) the composite envelope
Step 4 → **PREVIEW & CONFIRM** — render proposal/diff and require explicit `yes`
         (see [preview-and-confirm.md](authoring/preview-and-confirm.md)). The textual
         diff against the last-applied envelope snapshot feeds the brownfield change-set.
Step 5 → Submit the unchanged file once (PowerShell: az rest --body "@$payloadPath")
Step 6 → Poll LRO; on success, commit the envelope snapshot + ID map

Examples

End-to-end worked examples (create empty ontology → add entity type + non-timeseries binding → add relationship type + contextualization → add timeseries property + Eventhouse binding) live in examples.md. Complete fetch-mutate-send bash and PowerShell scripts live in definition-script-templates.md.


Agent Integration Notes

  • This skill is authoring-focused. Pair with a consumption skill (e.g., a Fabric Graph query skill) to validate the ontology end-to-end.

  • Parallelize schema discovery when the ontology binds to multiple source types:

    • Launch a background sqldw-cli task for lakehouse schemas (INFORMATION_SCHEMA.COLUMNS) — returns all tables + columns in one query.

    • Launch a background eventhouse-cli task for Eventhouse schemas (.show database schema as json) — returns all tables + columns in one call.

    • Both run concurrently. Merge results when both complete, then build the ontology model.

  • Merge step is critical — after discovery, deduplicate property names across properties[] and timeseriesProperties[], unify valueType for same-named properties across entity types, and prefix collisions before building the envelope.

  • When orchestrating multi-step customer workstreams that span Ontology + Eventhouse + Lakehouse, route via an agent (e.g., FabricDataEngineer) rather than chaining skills directly.

  • Reasonable upstream dependencies to assume: lakehouse tables already exist and have the key columns the user described. If not, the caller should invoke a lakehouse authoring skill first.

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an MCP server

Security

No issues found by our static scan· No issues found

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Published by
Microsoft
Publisher
Microsoft (Official)
Source
microsoft/skills-for-fabric · skills/ontology-cli · commit f8fac11
Licence
MIT License (redistribution allowed)
  • LICENSE (added by AutoGineer from LICENSE)
  • ATTRIBUTION.md (written by AutoGineer)
Synced
Also distributed in
microsoft/skills-for-fabric

AutoGineer is not affiliated with or endorsed by Microsoft or Microsoft.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download ontology-cli.zip