Dataflows CLI
MicrosoftOfficialPublished by Microsoft
- Skill
- Tool integration
- Automation and integrations
- MIT
- Low risk
- Has scripts
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The dataflows-cli skill provides a structured interface for interacting with Fabric Dataflow Gen2 items. It operates as a mode-based dispatcher, requiring users to select between authoring, consumption, or upgrade modes. The skill handles complex tasks such as M language definition updates, connection management, output destination configuration, and the migration of legacy Gen1 dataflows to Gen2.1. It emphasizes safe, intentional operations by requiring users to read specific reference documentation before executing state-changing API calls.
What it does
- Creates and updates Fabric Dataflow Gen2 items and their definitions.
- Manages connections, credentials, and output destinations for dataflows.
- Previews Power Query M definitions using executeQuery to validate logic before persistence.
- Facilitates the upgrade of Gen1 dataflows to Gen2.1 via save-as operations.
- Retrieves refresh history and inspects existing dataflow definitions.
- Provides a framework for executing and testing ad-hoc M queries.
When to use it
- When you need to programmatically create or modify Fabric Dataflow Gen2 items.
- When performing maintenance on Power Query M definitions or data connections.
- When migrating legacy Gen1 dataflows to the Gen2.1 architecture.
- When you need to audit refresh history or validate dataflow configurations.
- When testing M query logic against live data sources before saving changes.
Advantages
- Enforces a structured, mode-based workflow to prevent accidental mutations.
- Provides granular control over Dataflow Gen2 definitions and metadata.
- Supports complex lifecycle tasks like Gen1-to-Gen2 upgrades.
- Integrates directly with Fabric REST APIs for automated data pipeline management.
Usage examples
- “Show me the refresh history and parameters of the SalesIngest dataflow.”
- The agent enters consumption mode, reads the consumption reference, and retrieves the requested refresh history and parameters for the specified dataflow.
- “Preview this M query, then save it into the SalesIngest dataflow definition.”
- The agent enters authoring mode, previews the M query to validate it, and then executes the updateDefinition call to persist the changes.
- “Is my Gen1 dataflow ready to upgrade to Gen2.1?”
- The agent enters upgrade mode, performs a readiness assessment, and reports on the risk and requirements for upgrading the specified Gen1 dataflow.
Install
Download the skill as a ZIP:
https://mp-staging.autogineer.com/agent-skills/microsoft/dataflows-cli/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip dataflows-cli.zip -d .agents/skills
Documentation by Microsoft
Telemetry — MANDATORY. Every
api.fabric.microsoft.comcall must carryx-ms-fabric-skill: dataflows-cli(az rest:--headers "x-ms-fabric-skill=dataflows-cli"), including every LRO poll,fabric_lroand retry. Snippets omit it — add it anyway.
CRITICAL NOTES
To find the workspace details (including its ID) from workspace name: list all workspaces and, then, use JMESPath filtering
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
Skill disambiguation: use
dataflows-clifor every Fabric Dataflow Gen2 item and Power Query M concern. Synapse / ADF pipeline and dataset JSON translation ispipeline-migration; Spark notebook transformations arespark-cli; T-SQL transformations aresqldw-cli.
Fabric Dataflow Gen2 -- CLI Skill
This one skill owns Fabric Dataflow Gen2 items: connections, Power Query M, output destinations, saved definitions, refresh history, and Gen1 upgrades.
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.
Read it once per session. A file you have already read stays in context, so do not re-read it on a later turn; open the deeper files under references/<mode>/ only when a row in that file's reference table matches what you still need, and open only that row's file.
Mode selection
| Mode | Use when the request ... | Example triggers | Read this first |
|---|---|---|---|
authoring | creates or changes a dataflow: connections, credentials, M edits previewed before save, output destinations, updateDefinition | executeQuery preview, updateDefinition, connection setup, supportedConnectionTypes, credentialType, add output destination | references/authoring.md |
consumption | reads a saved dataflow: definition, parameters, refresh history, or runs a saved/ad-hoc read-only M query and parses Arrow | getDefinition, executeQuery saved query, ad-hoc query, Arrow IPC, refresh history | references/consumption.md |
upgrade | upgrades a Gen1 dataflow to Gen2.1 via save-as and rebind, or assesses upgrade readiness/risk | Gen1 Gen2 readiness, saveAsNativeArtifact, clone Gen1 dataflow, upgrade risk assessment | references/upgrade.md |
Mode boundary rule
consumption is read-only. A preview run that exists to validate M before updateDefinition, and any capability lookup (supportedConnectionTypes, credentialType), belong to authoring even though they issue the same executeQuery call -- classify by intent, not by endpoint.
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.
| Mode | Terminal write |
|---|---|
authoring | POST /v1/workspaces/{ws}/dataflows/{id}/updateDefinition to persist the M, connections and output destinations; a NEW dataflow needs POST /v1/workspaces/{ws}/items (or /dataflows) first. executeQuery is a preview and writes nothing. If the user asked for a refresh, also POST .../jobs/instances?jobType=Refresh. |
consumption | none -- this mode is read-only |
upgrade | saveAsNativeArtifact to create the Gen2.1 artefact, then rebind. A readiness report alone does not upgrade anything. |
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.
upgrade generation boundary -- HARD STOP (mandatory)
Save-as runs only from Gen1 to Gen2.1. For an execution request whose source is Gen2, or whose source generation is not established, state that no public save-as or in-place upgrade endpoint is available and stop before any API call. This does not block a read-only readiness scan whose purpose is to discover and classify Gen1 candidates.
Do not read "choose the closest endpoint and proceed" as approval to export a definition and create a copy, do not switch to authoring to do it instead, and do not mutate anything. Ask the user to clarify the intended outcome and to approve explicitly.
consumption reporting
consumption has no terminal write, so its deliverable is the answer itself. When that mode runs executeQuery, name the operation you called and state the QueryName you sent, verbatim, next to the results, plus whether it was a saved shared member or a non-persisted ad-hoc customMashupDocument. Rows alone do not tell the user which call produced them.
This applies to consumption only. In authoring, executeQuery is an intermediate preview step -- do not add reporting turns around it; go on to the terminal write.
Shared essentials (all modes)
Resolve the workspace and item first; every mode depends on it.
| Task | Reference | Notes |
|---|---|---|
| Finding Workspaces and Items in Fabric | COMMON-CLI.md | Mandatory -- read before resolving any workspace or item id |
| Fabric Topology & Key Concepts | COMMON-CORE.md | Item types, workspaces, capacities |
| Environment URLs | COMMON-CORE.md | Sovereign / non-public cloud hosts |
| Authentication & Token Acquisition | COMMON-CORE.md | Wrong audience = 401; read before any auth issue |
| Authentication Recipes | COMMON-CLI.md | az login flows and token acquisition |
| Core Control-Plane REST APIs | COMMON-CORE.md | Pagination, LRO polling, rate limiting |
| Gotchas & Troubleshooting | COMMON-CLI.md | az rest audience, shell escaping, token expiry |
Rules
MUST
Select exactly one mode from the table above before doing anything else.
Read
references/<mode>.mdend 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.Resolve workspace and item ids by listing and filtering, never by guessing a GUID.
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.
When a question about what Fabric supports IS the deliverable -- supported connection types, a connector's parameters, available credential types -- answer it from the endpoint the reference names rather than from the reference's own list, and name that endpoint in your answer. Whichever mode you are in: the reader cannot tell a live value from a quoted one unless you say where it came from. This is wording in the final answer, not an extra step.
Produce every artefact the user asked for, under the name they used. Running the right API calls is not a substitute for the requested report: if the request names a deliverable, emit it, and keep its heading even when the finding is "none" or "not applicable".
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.
Loading a different skill for work this skill already owns (see CRITICAL NOTES 3).
Examples
| User request | Mode | Reference to read |
|---|---|---|
| "Show me the refresh history and parameters of the SalesIngest dataflow." | consumption | references/consumption.md |
| "Preview this M query, then save it into the SalesIngest dataflow definition." | authoring | references/authoring.md |
| "Is my Gen1 dataflow ready to upgrade to Gen2.1?" | upgrade | references/upgrade.md |
Files
- ATTRIBUTION.mdAdded by AutoGineer871 B
- LICENSELicence file added by AutoGineer1.1 KiB
- SKILL.md9.5 KiB
- apm.yml445 B
- references/authoring.md55.3 KiB
- references/authoring/authoring-cli-quickref.md23.4 KiB
- references/authoring/authoring-script-templates.md79.7 KiB
- references/authoring/charts/bar_chart.pyScript3.9 KiB
- references/authoring/charts/line_chart.pyScript3.5 KiB
- references/authoring/connection-management.md39.8 KiB
- references/authoring/connectors.md14.7 KiB
- references/authoring/m-language.md9.7 KiB
- references/authoring/mashup-preview.md28.3 KiB
- references/authoring/output-destinations.md39.6 KiB
- references/consumption.md28.7 KiB
- references/consumption/chart-visualization.md2.6 KiB
- references/consumption/charts/bar_chart.pyScript3.9 KiB
- references/consumption/charts/line_chart.pyScript3.5 KiB
- references/consumption/consumption-cli-quickref.md6.7 KiB
- references/consumption/discovery-queries.md7.0 KiB
- references/consumption/script-templates.md17.1 KiB
- references/upgrade.md21.0 KiB
- references/upgrade/risk-assessment-guide.md11.2 KiB
- references/upgrade/upgrade-cli-quickref.md8.8 KiB
references/authoring.md
<!-- Mode reference for the `dataflows-cli` skill. Loaded on demand from `skills/dataflows-cli/SKILL.md` when the request matches the `authoring` mode. -->
PRE-MUTATION REQUIREMENTS GATE (mandatory) Before any mutation, confirm enough user intent for the requested operation. For create or update requests, establish the source, query/schema and transformation, destination behavior, and refresh behavior (including an explicit choice of no destination or no refresh). If the request is generic, such as "set up the Dataflow I need for reporting," ask a structured clarifying question and stop before mutation. Do not infer requirements from unrelated workspace items or create a best-guess source, connection, or Dataflow.
dataflows-cli authoring mode — Dataflows Gen2 Authoring via CLI
Table of Contents
This skill (SKILL.md)
| Section | Notes |
|---|---|
| Tool Stack | az + jq + base64 + curl |
| Connection | Workspace/dataflow ID discovery |
| Agentic Workflows | Start here. A: create end-to-end; B: modify existing; C: preview loop |
| MUST DO / AVOID / PREFER | Authoring rules |
| Troubleshooting | Symptom → fix table |
| Examples | Runnable bash + PowerShell recipes |
| Output Expectations | Response conventions |
References (in references/)
| File | When to read |
|---|---|
| authoring-cli-quickref.md | One-liner recipes, status enums, base64 helpers, connection-binding quick patterns |
| authoring-script-templates.md | Full bash + PowerShell templates; end-to-end smoke test; LRO polling pattern |
| connection-management.md | List/create/inspect connections; supportedConnectionTypes; resolve ClusterId; ID format cheat sheet |
| connectors.md | M-side source connectors: live-verified function inventory, Lakehouse deep navigation, gateway scope for Web.Page / Web.BrowserContents, and Html.Table / Csv.Document / Json.Document patterns |
| m-language.md | M language semantics for Dataflow Gen2: try record shapes, per-cell error wrapping in column transforms, each scoping in row vs sub-table contexts, optional field access [?] / Record.FieldOrDefault, quoted identifiers, sandbox-disabled symbols (File.Contents) |
| mashup-preview.md | executeQuery contract: bootstrap branch, auto-wrap rule, hard avoid for unbounded preview |
| output-destinations.md | Output destination patterns: Lakehouse Table, Lakehouse Files, Warehouse, ADX, Azure SQL. DataDestinations annotation, hidden query, loadEnabled rules, connection limitations |
Common refs (in ../../../common/)
| File | When to read |
|---|---|
| COMMON-CLI.md | az login, token acquisition, az rest, pagination, LRO polling, CLI gotchas. § Finding Workspaces and Items in Fabric is mandatory. |
| COMMON-CORE.md | Fabric topology, environment URLs, authentication, core REST API surface |
| ITEM-DEFINITIONS-CORE.md | Definition envelope; per-item-type payload contracts |
| DATAFLOWS-AUTHORING-CORE.md | Authoring capability matrix; 3-part definition structure; M structure; connection model; ALM / Git integration |
Sister skills
| Skill | Use for |
|---|---|
| dataflows-cli consumption mode | Execute persisted queries; ad-hoc read-only customMashupDocument with no intent to persist; Arrow → CSV/pandas conversion; refresh status/history. |
Tool Stack
| Tool | Role | Install |
|---|---|---|
az CLI | Primary: Auth (az login), REST API calls (az rest), token acquisition. | Pre-installed in most dev environments |
jq | Parse and manipulate JSON responses and definition payloads. | Pre-installed or trivial |
base64 | Encode/decode definition parts for the REST API. | Built into bash / [Convert]::ToBase64String() in PowerShell |
curl | Alternative to az rest when raw HTTP control is needed. | Pre-installed |
uuidgen | Generate per-query / per-platform GUIDs for queryId and logicalId when building a new dataflow definition (Workflow A). | Pre-installed on Linux/macOS; on Windows use PowerShell [guid]::NewGuid().Guid or run via WSL |
Agent check — verify
az,jq, andcurlare available before first operation.uuidgenis only needed for Workflow A (Create). For installation and auth setup see COMMON-CLI.md.
Connection
Discover Workspace and Dataflow IDs
Per COMMON-CLI.md Finding Workspaces and Items in Fabric:
# List workspaces — find workspace ID by name
az rest --method get \
--resource "https://api.fabric.microsoft.com" \
--url "https://api.fabric.microsoft.com/v1/workspaces" \
--query "value[?displayName=='MyWorkspace'].id" --output tsv
# List dataflows in workspace — find dataflow ID by name
WS_ID="<workspaceId>"
az rest --method get \
--resource "https://api.fabric.microsoft.com" \
--url "https://api.fabric.microsoft.com/v1/workspaces/$WS_ID/dataflows" \
--query "value[?displayName=='MyDataflow'].id" --output tsvReusable Connection Variables
WS_ID="<workspaceId>"
DF_ID="<dataflowId>"
API="https://api.fabric.microsoft.com/v1"
RESOURCE="https://api.fabric.microsoft.com"Agentic Workflows
Three workflows cover the typical authoring tasks:
A. Create a New Dataflow End-to-End — discover/create a connection, create the dataflow, save M + bindings, validate, optionally refresh.
B. Modify an Existing Dataflow — read-modify-write the definition; the canonical Discover → Formulate → Execute → Verify loop.
C. Preview-Driven Authoring Loop — iterate on candidate M via
executeQuerybefore persisting viaupdateDefinition.D. Output Destination — write query results to Lakehouse (table/files), Warehouse, ADX, or Azure SQL via
DataDestinationsannotation. Full reference: output-destinations.md.
A. Create a New Dataflow End-to-End
Use this when the dataflow does not yet exist. Covers the full happy path: discover-or-create a connection, create the dataflow shell, save M + bindings in one updateDefinition, validate, optionally refresh.
Steps:
List existing connections and filter by
connectionDetails.typeand the target URL/host — reuse if a match exists (GET /v1/connections+ JMESPath).If no match, create the connection. First
GET /v1/connections/supportedConnectionTypesto discover required parameters and supported credential types, thenPOST /v1/connections(sync 201). Body shape and credential schemas: connection-management.md.Resolve
ClusterIdfor the composite binding.GET https://api.powerbi.com/v2.0/myorg/me/gatewayClusterDatasourceswith--query "value[?id=='$CONN_ID'] | [0].clusterId", audience--resource "https://analysis.windows.net/powerbi/api"(no trailing slash). The per-id route returnsPowerBIEntityNotFoundfor cloud connections. Newly-created connections may take a few seconds to surface — retry on empty. Detail: connection-management.md § Resolving ClusterId.Create the dataflow shell.
POST /v1/workspaces/{ws}/dataflowswith{"displayName":"<displayName>"}returns sync 201. Thedefinitionfield is optional at create time and can be set in the next step. If you instead supply the fulldefinition(all three parts) in this create POST, that call is the persist surface -- namePOST /v1/workspaces/{ws}/dataflows(notupdateDefinition) as the persist path in your summary.Save M + connection bindings in one call.
POST /v1/workspaces/{ws}/dataflows/{df}/updateDefinition?updateMetadata=truewith three parts:mashup.pq(realWeb.Contents/Sql.Database/ …),queryMetadata.json(withconnections[]populated; eachconnectionIdis the stringified composite{"ClusterId":"…","DatasourceId":"…"}), and.platform. Typically returns sync 200; may return 202 + LROLocationon large bodies — handle both.Verify the binding persisted. Re-call
getDefinition, decodequeryMetadata.json, and confirmconnections[]is intact. Do not useGET /items/{id}/connectionsfor verification — that endpoint reflects refresh-materialized state, not the persisted definition, and returns 0 even after a successful bind. See AVOID. Guarded preview-only fallback: If connection binding cannot proceed because Power BI v2gatewayClusterDatasourcesreturns 401/403/Unauthorized or noClusterIdis visible after retries, do not stop before creating the dataflow. Create the shell, persist the saved query definitions without claiming a valid source binding, callexecuteQuerywithQueryNameonly for each saved query, surface the exact credential or binding error from the Arrow stream, do not fabricate chart data, and do not refresh.(Encouraged) Offer to preview output as ASCII charts. Ask the user: "Would you like me to preview the data as charts before the first refresh?". In this create flow the definition is already saved in step 5, so the chart preview here is a post-save validation gate before you materialize via refresh — not a pre-save step. (If instead you want to validate candidate M before the first
updateDefinition— e.g. iterating on the M, or bootstrap-binding a credentialed source soexecuteQuerycan see it — use the pre-persist Preview-Driven Authoring Loop; the chart rendering is identical, only the ordering relative to the save differs.) If accepted, callexecuteQueryfor each entity, parse the Arrow IPC stream, render line charts (time-series) or horizontal bar charts (categories) viareferences/authoring/charts/line_chart.py/references/authoring/charts/bar_chart.py, and ask the user to confirm before proceeding. Details: mashup-preview.md § ASCII chart preview. If declined, proceed directly to step 8.(Optional) Trigger refresh to materialize.
POST .../jobs/instances?jobType=Refreshwith body{"executionData":{"executeOption":"ApplyChangesIfNeeded"}}.ApplyChangesIfNeededis required on the first refresh after any definition change — without it, Fabric refreshes the previously-applied definition. Poll the LRO untilstatusisCompleted(refresh enum) orFailed/Cancelled.
# Concise skeleton — full runnable bash is Example 1 below.
# PowerShell + LRO-polled variants: references/authoring-script-templates.md
WS_ID="<workspaceId>"; URL="<source-url>"
RES="https://api.fabric.microsoft.com"; API="$RES/v1"
PBI="https://analysis.windows.net/powerbi/api"
# 1. List existing & try reuse
CONN_ID=$(az rest --method get --resource "$RES" --url "$API/connections" \
--query "value[?connectionDetails.type=='Web' && connectionDetails.path=='$URL'] | [0].id" -o tsv)
# 2. Create connection if missing — see connection-management.md for full body
# 3. List+filter for ClusterId
CLUSTER_ID=$(az rest --method get --resource "$PBI" \
--url "https://api.powerbi.com/v2.0/myorg/me/gatewayClusterDatasources" \
--query "value[?id=='$CONN_ID'] | [0].clusterId" -o tsv)
# 4. Empty dataflow shell — sync 201
SHELL_BODY=$(mktemp --suffix=.json 2>/dev/null || mktemp)
printf '{"displayName":"my-df"}' > "$SHELL_BODY"
DF_ID=$(az rest --method post --resource "$RES" \
--url "$API/workspaces/$WS_ID/dataflows" \
--headers "Content-Type=application/json" \
--body "@$SHELL_BODY" --query id -o tsv)
rm -f "$SHELL_BODY"
# 5. One-shot updateDefinition with real M + connections[] (sync 200 typical)
# Body assembly (mashup.pq + queryMetadata.json + .platform, base64-encoded;
# queryMetadata.json.connections[].connectionId = composite ClusterId/DatasourceId):
# see Example 1 below.
# 6. Verify via getDefinition (NOT GET /items/{id}/connections — see AVOID)
# 7. (optional) executeQuery — Workflow C
# 8. (optional) Refresh with executeOption=ApplyChangesIfNeeded — Example 2One-shot vs two-step bind+save. Steps 4-5 can be one call (default; saves an HTTP round trip) or split into a bootstrap-bind
updateDefinitionfollowed by a full-MupdateDefinition. Both work — see PREFER.
B. Modify an Existing Dataflow
Use this when the dataflow already exists. Canonical Discover → Formulate → Execute → Verify loop. If the dataflow does not yet exist, see Workflow A instead.
Discover — list workspaces, list dataflows,
getDefinition(decodemashup.pqandqueryMetadata.json). Validate allconnections[]entries viaGET /v1/connections/{id}.Formulate — modify M, re-encode parts, ensure every referenced
connectionIdexists in the caller's connection store.Execute —
POST .../updateDefinition?updateMetadata=truewith all 3 parts (full replacement). Optionally trigger refresh.Verify — re-call
getDefinitionto confirm changes; poll refresh LRO; for refresh failures, make at most oneexecuteQueryisolation attempt to localize a fixable M/source issue. On a terminal/non-retriable failure (isRetriable: false, workspace-wideUnknownException), surface the raw error and stop rather than re-triggering.
# Concise skeleton — full templates: references/authoring-script-templates.md
# Acquire $TOKEN per common/COMMON-CLI.md § Token-in-Variable Pattern (resource = $RESOURCE).
RESOURCE="https://api.fabric.microsoft.com"; API="$RESOURCE/v1"
# 1. Discover — getDefinition (handles 200 sync and 202 + LRO via curl)
HDR=$(mktemp); BODY=$(mktemp)
CODE=$(curl -sS -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Length: 0" \
"$API/workspaces/$WS_ID/dataflows/$DF_ID/getDefinition" \
-D "$HDR" -o "$BODY" -w "%{http_code}")
if [ "$CODE" = "202" ]; then
LOC=$(tr -d '\r' < "$HDR" | grep -i "^location:" | awk '{print $2}')
RETRY=$(tr -d '\r' < "$HDR" | grep -i "^retry-after:" | awk '{print $2}'); RETRY=${RETRY:-5}
while :; do
sleep "$RETRY"
OP=$(az rest --method get --resource "$RESOURCE" --url "$LOC")
case "$(echo "$OP" | jq -r '.status // empty')" in
Succeeded) RESULT=$(az rest --method get --resource "$RESOURCE" --url "${LOC%/}/result"); break ;;
Failed|Cancelled) echo "ERROR: getDefinition $(echo "$OP" | jq -r '.status')" >&2; exit 1 ;;
esac
done
else
RESULT=$(cat "$BODY")
fi
rm -f "$HDR" "$BODY"
# Validate bound connections (connectionId is a composite JSON string — iterate safely)
QUERY_META=$(echo "$RESULT" | jq -r '.definition.parts[] | select(.path=="queryMetadata.json") | .payload' | base64 -d)
echo "$QUERY_META" | jq -c '.connections[]?' | while IFS= read -r conn; do
RAW=$(echo "$conn" | jq -r '.connectionId')
DATASOURCE_ID=$(echo "$RAW" | jq -r '.DatasourceId? // empty' 2>/dev/null)
[ -z "$DATASOURCE_ID" ] && DATASOURCE_ID="$RAW"
# GET /v1/connections/$DATASOURCE_ID to confirm access
done
# 2-3. Formulate & Execute — see Example 3
# 4. Verify — trigger refresh via curl (az rest cannot capture Location header).
# Full LRO polling: references/authoring-script-templates.md.C. Preview-Driven Authoring Loop (pre-save executeQuery — see mashup-preview.md)
When the change touches Power Query M (new query, edited mashup, new source, changed parameters), preview the candidate customMashupDocument against the dataflow's bound connections before persisting. Catches syntax, schema, and credential errors at authoring time. Full ordered steps, bootstrap branch, auto-wrap rule, hard-avoid for unbounded preview, ASCII chart preview, and Apache Arrow handling: mashup-preview.md § Preview-Driven Authoring Loop.
Intent split. This workflow is for the pre-save intent. To execute a saved query (
QueryNameonly) or run an ad-hoc read-onlycustomMashupDocumentwith no intent to persist, usedataflows-cli consumption mode.mashup-preview.mdis the shared API reference for both intents.
Skip the preview only for metadata-only edits (display name, schedule, loadEnabled toggle) or when the agent records an explicit skip reason (bootstrap, prohibitive cost, side-effecting source).
D. Output Destination
Use this when the dataflow should write query results to an external store (Lakehouse table, Lakehouse files, Warehouse, ADX, Azure SQL). Extends Workflow A with DataDestinations annotations and a hidden destination query. Full reference with complete examples: output-destinations.md.
Key requirements:
Source query carries a
[DataDestinations = {[...]}]annotation referencing the destination query by name.Hidden destination query (suffixed
_DataDestination) navigates to the target storage using null-safe?[Data]?(tables) or?[Content]?(files) operators.queryMetadata.json must set
"loadEnabled": falseon the destination query — refresh fails without it. State this in your summary using the literal part name (e.g., "setloadEnabled: falseon the destination query inqueryMetadata.json").Always use
IsNewTarget = truefor API-created dataflows, even for existing tables.Bind the appropriate connection (Lakehouse: kind
"Lakehouse"; Warehouse: kind"Warehouse"; ADX: kind"AzureDataExplorer"; Azure SQL: kind"Sql") with compositeClusterId/DatasourceIdID.First refresh must use
ApplyChangesIfNeededto publish the draft and reconcile annotations.All source columns must be typed —
Any-type columns are rejected by all destination types.Name the definition parts in your written summary. Because the CLI transcript truncates long command bodies, the final summary (prose, not just shell commands) MUST name the three definition parts by their literal paths —
mashup.pq,queryMetadata.json, and.platform— so the part names survive in the answer (e.g., "Savedmashup.pq+queryMetadata.json+.platformviaupdateDefinition"). Do not abbreviatequeryMetadata.jsonto "query metadata" or the inner fieldqueriesMetadata.
Supported destinations:
| Destination | Connection Kind | Destination Query Function | Notes |
|---|---|---|---|
| Lakehouse Table | Lakehouse | Lakehouse.Contents(...) | Path: "Lakehouse" |
| Lakehouse Files | Lakehouse | Lakehouse.Contents(...) | TypeSettings = [Kind = "File"], ?[Content]? |
| Warehouse | Warehouse | Fabric.Warehouse(...) | Path: "Warehouse", Schema/Item navigation |
| Azure Data Explorer | AzureDataExplorer | AzureDataExplorer.Contents(...) | Path must match connection exactly (trailing slash!) |
| Azure SQL | Sql | Sql.Database(...) | Path: "server;database" |
Minimal steps: Create dataflow → Find/create connection → Resolve ClusterId → Save definition with OD annotations → Verify → Refresh.
# Skeleton — full PowerShell recipe: references/output-destinations.md § Complete Example
WS_ID="<workspaceId>"; LH_ID="<lakehouseId>"; RES="https://api.fabric.microsoft.com"
# M pattern (two queries):
# 1. Source with [DataDestinations] annotation
# 2. Hidden _DataDestination query with ?[Data]? null-safe navigation
# queryMetadata: source loadEnabled=true, destination loadEnabled=false + isHidden=true
# Refresh: {"executionData":{"executeOption":"ApplyChangesIfNeeded"}}Gotchas, Rules, Troubleshooting
For full authoring gotchas: DATAFLOWS-AUTHORING-CORE.md Gotchas and Troubleshooting. For CLI-specific issues: COMMON-CLI.md Gotchas & Troubleshooting (CLI-Specific). For connection discovery: authoring-cli-quickref.md § Connection Discovery and Validation.
MUST DO
Offer to preview every entity before the first refresh of a new dataflow — after creating the shell and binding connections via
updateDefinition(which persists the definition), ask the user if they want to see preview charts before materializing via refresh. In the preview-driven loop the preview instead precedes the persistingupdateDefinition. If accepted, follow mashup-preview.md § ASCII chart preview. Skip only for metadata-only edits (display name, schedule) or when the agent records an explicit skip reason.az loginfirst — allaz restcalls use the active session. No session → 401.Use
--resource "https://api.fabric.microsoft.com"for Fabric APIs. For Power BI v2 (gatewayClusterDatasources), use--resource "https://analysis.windows.net/powerbi/api"without a trailing slash — the slashed form failsAADSTS500011 invalid_resource.Base64-encode all 3 definition parts —
mashup.pq+queryMetadata.json+.platform, eachpayloadType: "InlineBase64".updateDefinitionis a full replacement; sending 1 or 2 parts silently drops queries.Handle sync AND async responses.
POST /dataflows,updateDefinition, andgetDefinitiontypically return sync (200/201) but may return 202 + LROLocationon large bodies — handle both. See authoring-script-templates.md § Fabric LRO Polling Pattern.Set
formatVersion: "202502"inqueryMetadata.jsonand include a top-levelnamematchingdisplayName— omitting either causes save-time failures or stale display-name state.loadEnabledis opt-out, not opt-in. Fabric auto-loads every query to the staging Lakehouse by default; setloadEnabled: falseonly on helper queries you do not want written. Note:loadEnabled: trueis also stripped fromqueryMetadata.jsonon round-trip viagetDefinition(it's the default) — its absence on read-back is not a bug. Detail: DATAFLOWS-AUTHORING-CORE.md § loadEnabled semantics.Use the right ID format per context. REST
/v1/connectionsoperations take the plain GUID fromconnection.id;queryMetadata.json connections[].connectionIdtakes the stringified composite{"ClusterId":"…","DatasourceId":"…"}. See connection-management.md § Connection ID Format Cheat Sheet.Resolve
ClusterIdvia list+filter.GET .../gatewayClusterDatasourcesfiltered byvalue[?id=='$CONN_ID']. The per-id route returnsPowerBIEntityNotFoundfor cloud connections; newly-created connections may need a 5-15 s retry. See connection-management.md § Resolving ClusterId.executeQuerybody uses a top-levelQueryNamefield (PascalCase canonical; the field name itself is case-insensitive on the wire — lowercasequeryNamealso evaluates). Value must name asharedmember from the persisted M or the suppliedcustomMashupDocument. The{"queries":[…]}array shape always fails withDataflowExecuteQueryError: Invalid query name; a wrong query name returnsQueryNotFound. Full contract: mashup-preview.md § Request body.Use the exact, case-sensitive API names. The endpoint is
executeQuery(singular, neverexecuteQueries) and the request-body field iscustomMashupDocument(nevermashupDocument, never base64-encoded — it is a plain UTF-8 M string). The same M body becomes the savedmashup.pqpart referenced ascustomMashupDocument. Vocabulary table: mashup-preview.md § Vocabulary.First refresh after any
updateDefinitionMUST useexecuteOption: "ApplyChangesIfNeeded". Body:{"executionData":{"executeOption":"ApplyChangesIfNeeded"}}. Without it, Fabric refreshes the previously-applied definition.Treat a terminal refresh failure as a stop condition — do not debug-loop. When a refresh/LRO job reaches terminal
Failed/Cancelled, or a backend error carriesisRetriable: false(or a workspace-wideUnknownException), report the raw error verbatim and stop. These are backend/infra outcomes the agent cannot fix by retrying — do not re-trigger the refresh, keep re-polling, or open an extended investigation. At most, make oneexecuteQueryisolation attempt to localize a fixable M/source cause; if that does not reveal a definition-side issue, end and surface the error.Call
GET /v1/connections/supportedConnectionTypesbeforePOST /v1/connections-- never guess parameter names or credential types; they vary by connector, tenant, and time. When summarizing a connector's required parameters orcredentialTypeset for a user, use the exact, case-sensitive endpoint pathGET /v1/connections/supportedConnectionTypes. This applies even to a pure lookup ("which parameters/credentialType does connector X support?"): run the liveGET /v1/connections/supportedConnectionTypesagainst the tenant. The bundledconnection-management.mdreference guides the response shape but is not a substitute for the tenant-specific, case-sensitive values, which vary by connector and over time.Validate referenced connections before refresh. For each
connectionIdinqueryMetadata.json,GET /v1/connections/{id}(plain GUID extracted from the composite). CrypticEntityUserFailureat refresh time is often a missing/inaccessible connection. See connection-management.md.Bootstrap-bind connections before previewing credentialed M. A
connections[]array in the initial create payload is not yet visible toexecuteQuery; persist it through at least oneupdateDefinitionfirst. Detail: mashup-preview.md § Bootstrap branch.Send a full
section Section1; ...document incustomMashupDocument—executeQuerydoes not auto-wrap raw expressions. See mashup-preview.md § customMashupDocument format.Preview candidate M via
executeQuerybeforeupdateDefinition— unless the change is metadata-only or the agent records an explicit skip reason. Treat preview success as "M evaluates"; treat the next refresh as the real go/no-go.Pass JSON bodies via
--body "@<file>", not inline. Write to$env:TEMP\<name>.json(PowerShell, UTF-8 no-BOM via[IO.File]::WriteAllText) or/tmp/<name>.json(bash). Inline--body "<json>"is fragile in bash and broken on Windows becausecmd.exe's argument parser mangles embedded quotes. See authoring-script-templates.md § PowerShell — Create Dataflow with Definition.Prefer
WorkspaceIdentity/ServicePrincipalcredentials for unattended refresh.OAuth2+singleSignOnType: Noneworks for interactiveexecuteQuerybut is fragile under tenant Conditional Access for service-context refresh. Check supported types viasupportedConnectionTypes.
AVOID
Materializing a new dataflow (first refresh) without offering the user a preview — the user cannot validate that the M code matches their intent by reading code alone. Always offer to preview each entity's output as an ASCII chart before the first refresh (and, in the preview-driven loop, before the persisting
updateDefinition). The user may decline, but the offer should always be made.Adding a
formatproperty todefinition— Items API usesparts[]only;"format": "json"returns400 InvalidDefinitionFormat.Hardcoded workspace/dataflow GUIDs — discover via REST API (Connection section).
Using
GET /v1/workspaces/{ws}/items/{itemId}/connectionsto verify a freshly-bound dataflow. It reflects refresh-materialized state, not the persisted definition, and returns 0 after a successful bind. Verify viagetDefinition+ decodequeryMetadata.json.connections[].Assuming
updateDefinition/POST /dataflowsis always LRO. Typical responses are sync (200/201); handle both shapes — see MUST DO above.Requesting the PBI v2 token with a trailing slash (
--resource "https://analysis.windows.net/powerbi/api/") — failsAADSTS500011 invalid_resource. Use the no-slash form.Per-id
gatewayClusterDatasources/{id}for cloud connections — returnsPowerBIEntityNotFound. Use list+filter (MUST DO above).{"queries":[…]}array body shape forexecuteQuery— always returns400 DataflowExecuteQueryError: Invalid query nameregardless of inner casing. Use a top-levelQueryName(orqueryName— the field is case-insensitive); pick exactly one query per call.Using
GETforgetDefinition— it's a POST endpoint;GETreturns 405.Constructing operation URLs manually — always follow the
Locationheader from a 202 response.Duplicate
displayNamevalues — not enforced but causes confusion.Binding connections by display name — connection IDs are the source of truth; names can change.
Assuming all connections are accessible to all users. Visibility is per-caller:
GET /v1/connections/{id}may return 403/404 for callers without access. An emptyGET /v1/connectionsis not proof a connection is absent.Hand-crafting connection request bodies without
supportedConnectionTypes— guessing produces400 InvalidConnectionDetails/400 InvalidCredentialDetails.Plaintext credentials in generated examples or committed scripts — never render or commit plaintext credential values. Show only
passwordReference/keyReference/tokenReference/servicePrincipalSecretReferencein generated connection bodies.Templating on-prem gateway connection bodies as plaintext —
OnPremisesGatewayneeds RSA-encrypted credentials per gateway member.Converting a published single-source dataflow to multi-source in place — bindings drift into inconsistent state; create fresh and retire the old.
Persisting un-previewed candidate M via
updateDefinition—executeQueryis significantly faster than theupdateDefinition-then-debug-refresh loop. See mashup-preview.md.Unbounded preview against production-volume sources —
executeQueryreturns the full evaluated dataset. InjectTable.FirstN/TOP N/ date predicate into the preview-only document; strip before saving. See mashup-preview.md § Hard avoid.Confusing
executeQuerywithEvaluateQuery.EvaluateQueryrequires a prior successful refresh;executeQuery+customMashupDocumentdoes not. UseexecuteQueryfor the authoring preview loop.Inline
--bodyon Windows/PowerShell —cmd.exemangles quotes; always use--body "@$env:TEMP\<name>.json".
PREFER
One-shot
updateDefinitioncarrying real M +connections[]over a bootstrap-bind + save pair — saves an HTTP round trip; both are functionally equivalent. Use the two-step form for didactic walk-throughs or when the bootstrap M needs to differ from the production M (e.g., the bootstrap branch in mashup-preview.md).az restover rawcurl— handles token acquisition and refresh automatically. Fall back tocurlonly when you need to capture response headers (e.g., 202 LROLocation) —az restcannot.getDefinitionbeforeupdateDefinition— read-modify-write prevents accidental data loss;updateDefinitionis a full replacement.?updateMetadata=trueonupdateDefinition— ensures.platformchanges (display name) are applied.jqfor JSON manipulation — build definition payloads programmatically."Automatic"for parameter type in job execution — lets the engine infer from definition.Env vars (
WS_ID,DF_ID,API,RESOURCE) for script reuse.Batch connection validation — loop over
queryMetadata.json connections[]andGET /v1/connections/{id}in one pass before refresh; optionallyPOST /v1/connections/{id}/testConnectionto catch rotated credentials.Offer preview charts before committing a new dataflow — render sample data as an ASCII chart so the user can validate the output shape and values.
TROUBLESHOOTING
| Symptom | Fix |
|---|---|
| 401 Unauthorized | Verify az login is active; check --resource "https://api.fabric.microsoft.com" (or https://analysis.windows.net/powerbi/api no trailing slash for PBI v2). |
405 Method Not Allowed on getDefinition | Use POST, not GET. |
updateDefinition silently drops queries | Send all 3 parts (mashup.pq, queryMetadata.json, .platform). |
executeQuery → 400 DataflowExecuteQueryError: Invalid query name | Body uses the {"queries":[…]} array shape — that always fails. Switch to a top-level {"QueryName":"<shared>"} (PascalCase canonical; the field is case-insensitive on the wire). |
executeQuery → 400 DataflowExecuteQueryError: ErrorCode: QueryNotFound | The value of QueryName doesn't match any shared member of the persisted M or supplied customMashupDocument. List queries via getDefinition → decode mashup.pq. |
GET /items/{id}/connections returns 0 after a successful bind | That endpoint reflects refresh-materialized state, not the definition. Verify via getDefinition → decode queryMetadata.json.connections[]. |
404 / PowerBIEntityNotFound fetching ClusterId from gatewayClusterDatasources/{id} | Per-id route does not resolve cloud connections. Use list + filter: GET .../gatewayClusterDatasources --query "value[?id=='$CONN_ID'] | [0].clusterId", audience https://analysis.windows.net/powerbi/api (no slash). Newly-created connections may need 5-15 s to surface — retry. See connection-management.md § Resolving ClusterId. |
Refresh fails on first run after updateDefinition (stale data, missing changes) | Body must include {"executionData":{"executeOption":"ApplyChangesIfNeeded"}} on the first refresh after any definition change. |
| Refresh fails with "Connection not found" | Extract connectionId (composite) from queryMetadata.json, parse DatasourceId, confirm via GET /v1/connections/{id}. |
connections[] missing after updateDefinition | Read-modify-write rebuilt queryMetadata.json from a snapshot without bindings. Re-bind and updateDefinition again before refresh. |
| Refresh reports "connection not found" after create+bind | Wrong ID format in queryMetadata.json. REST id is plain GUID; connectionId is the stringified composite {"ClusterId":"…","DatasourceId":"…"}. |
formatVersion mismatch error | Set formatVersion: "202502" in queryMetadata.json. |
| Fast copy not engaged | Add [StagingDefinition = [Kind = "FastCopy"]] before section in mashup.pq. |
| LRO polling returns 404 | Use the Location header URL — don't construct operation URLs manually. |
| 429 Too Many Requests | Respect Retry-After; exponential backoff. |
| Base64 decode produces garbage | Strip trailing newlines; use base64 -w0 (Linux). |
Inline --body "<json>" returns 400 / empty body on Windows | cmd.exe arg parser mangles quotes when launching az.exe. Write to $env:TEMP\body.json (UTF-8, no BOM) and pass --body "@$env:TEMP\body.json". See authoring-script-templates.md § PowerShell — Create Dataflow with Definition. |
Refresh fails with EntityUserFailure / "Something went wrong" and no detail | (1) Confirm updateDefinition was called after create; (2) check credential type — OAuth2+singleSignOnType: None often fails under tenant Conditional Access for unattended refresh; prefer WorkspaceIdentity/ServicePrincipal; (3) executeQuery against the dataflow to isolate M+source; (4) GET https://api.powerbi.com/v1.0/myorg/groups/{ws}/dataflows/{df}/transactions (PBI v1.0) sometimes returns richer per-entity errors. |
Examples
Platform note — examples below are bash. On Windows / PowerShell the bash patterns (
MASHUP='...'heredoc,echo -n | base64 -w0,tr -d '\r' | grep -i location | awk) cause real escaping pain and refresh-pattern flakes. PowerShell variants are linked from the two highest-friction examples (Create and Refresh) below. For full PowerShell templates (Create, Refresh, Validate Connections, Bind Connection, Create Cloud Connection): authoring-script-templates.md § PowerShell. On PowerShell, prefer--body "@$env:TEMP\body.json"and write the body via[IO.File]::WriteAllText($path, $body, [System.Text.UTF8Encoding]::new($false))overOut-File(which writes a UTF-8 BOM on Windows PowerShell 5.1 and breaksaz.exebody parsing) and over inline--body "{...}"(whichcmd.exemangles).
Example 1: Create a Dataflow Gen2 from Scratch
Prompt: "Create a new Dataflow Gen2 that reads a public CSV via the Web connector, and verify it."
Agent response — runnable bash implementation of Workflow A. PowerShell variant: authoring-script-templates.md § End-to-End Smoke Test.
# Prereqs: az login, jq, base64, uuidgen. Workspace must support Dataflow Gen2.
WS_ID="<workspaceId>"
DF_NAME="my-titanic-df"
CONN_NAME="my-titanic-web-conn"
URL="https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"
RES="https://api.fabric.microsoft.com"; API="$RES/v1"
PBI="https://analysis.windows.net/powerbi/api" # NO trailing slash
# Step 1: List existing connections, try to reuse by name.
CONN_ID=$(az rest --method get --resource "$RES" --url "$API/connections" \
--query "value[?displayName=='$CONN_NAME'] | [0].id" -o tsv)
# Step 2: Create if missing (Web + Anonymous; see connection-management.md for other shapes).
if [ -z "$CONN_ID" ] || [ "$CONN_ID" = "null" ]; then
BODY_FILE=$(mktemp --suffix=.json 2>/dev/null || mktemp) # GNU + BSD/macOS compatible
cat > "$BODY_FILE" <<EOF
{
"displayName": "$CONN_NAME",
"connectivityType": "ShareableCloud",
"connectionDetails": {
"type": "Web", "creationMethod": "Web",
"parameters": [{"name": "url", "dataType": "Text", "value": "$URL"}]
},
"privacyLevel": "Organizational",
"credentialDetails": {
"singleSignOnType": "None", "connectionEncryption": "NotEncrypted",
"skipTestConnection": false,
"credentials": {"credentialType": "Anonymous"}
}
}
EOF
CONN_ID=$(az rest --method post --resource "$RES" --url "$API/connections" \
--headers "Content-Type=application/json" --body "@$BODY_FILE" --query id -o tsv)
rm -f "$BODY_FILE"
fi
# Step 3: Resolve ClusterId via list+filter; retry — PBI v2 lags by 5-15s on new conns.
for i in 1 2 3 4 5 6 7 8; do
CLUSTER_ID=$(az rest --method get --resource "$PBI" \
--url "https://api.powerbi.com/v2.0/myorg/me/gatewayClusterDatasources" \
--query "value[?id=='$CONN_ID'] | [0].clusterId" -o tsv 2>/dev/null)
[ -n "$CLUSTER_ID" ] && [ "$CLUSTER_ID" != "null" ] && break
sleep $((i*3))
done
# Fail-fast: an empty ClusterId silently corrupts the composite connectionId and the
# resulting updateDefinition / refresh failures are hard to debug. Stop here instead.
if [ -z "$CLUSTER_ID" ] || [ "$CLUSTER_ID" = "null" ]; then
echo "FAIL: ClusterId not resolved for $CONN_ID after retries. Verify the connection is visible at PBI v2 (api.powerbi.com/v2.0/myorg/me/gatewayClusterDatasources)." >&2
exit 1
fi
# Step 4: Create empty dataflow shell (sync 201).
SHELL_BODY=$(mktemp --suffix=.json 2>/dev/null || mktemp)
printf '{"displayName":"%s"}' "$DF_NAME" > "$SHELL_BODY"
DF_ID=$(az rest --method post --resource "$RES" \
--url "$API/workspaces/$WS_ID/dataflows" \
--headers "Content-Type=application/json" \
--body "@$SHELL_BODY" --query id -o tsv)
rm -f "$SHELL_BODY"
# Step 5: One-shot updateDefinition — real M + composite-bound connections[] + .platform.
MASHUP='section Section1;
shared Titanic = let
Source = Csv.Document(Web.Contents("'"$URL"'"), [Delimiter=",", Encoding=65001, QuoteStyle=QuoteStyle.Csv]),
Headers = Table.PromoteHeaders(Source, [PromoteAllScalars=true])
in Headers;'
COMPOSITE_ID="{\"ClusterId\":\"$CLUSTER_ID\",\"DatasourceId\":\"$CONN_ID\"}"
QUERY_META=$(jq -n --arg name "$DF_NAME" --arg cid "$COMPOSITE_ID" --arg url "$URL" --arg qid "$(uuidgen)" '{
formatVersion: "202502",
name: $name,
queriesMetadata: { Titanic: { queryId: $qid, queryName: "Titanic" } },
connections: [ { connectionId: $cid, kind: "Web", path: $url } ]
}')
PLATFORM=$(jq -n --arg name "$DF_NAME" --arg lid "$(uuidgen)" '{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/gitIntegration/platformProperties/2.0.0/schema.json",
metadata: { type: "Dataflow", displayName: $name },
config: { version: "2.0", logicalId: $lid }
}')
MASHUP_B64=$(echo -n "$MASHUP" | base64 -w0)
META_B64=$(echo -n "$QUERY_META" | base64 -w0)
PLAT_B64=$(echo -n "$PLATFORM" | base64 -w0)
BODY_FILE=$(mktemp --suffix=.json 2>/dev/null || mktemp) # GNU + BSD/macOS compatible
cat > "$BODY_FILE" <<EOF
{"definition":{"parts":[
{"path":"mashup.pq", "payload":"${MASHUP_B64}", "payloadType":"InlineBase64"},
{"path":"queryMetadata.json", "payload":"${META_B64}", "payloadType":"InlineBase64"},
{"path":".platform", "payload":"${PLAT_B64}", "payloadType":"InlineBase64"}
]}}
EOF
az rest --method post --resource "$RES" \
--url "$API/workspaces/$WS_ID/dataflows/$DF_ID/updateDefinition?updateMetadata=true" \
--headers "Content-Type=application/json" --body "@$BODY_FILE"
rm -f "$BODY_FILE"
# Step 6: Verify connections[] persisted via getDefinition (NOT /items/{id}/connections).
# Assumes the sync 200 fast-path (typical, ~1s). If the call ever returns 202 LRO,
# az rest can't expose the Location header — switch to the curl + poll pattern from
# Example 3 / authoring-script-templates.md and decode the polled 200 body instead.
PERSISTED=$(az rest --method post --resource "$RES" \
--url "$API/workspaces/$WS_ID/dataflows/$DF_ID/getDefinition" \
--headers "Content-Length=0" \
| jq -r '.definition.parts[] | select(.path=="queryMetadata.json") | .payload' | base64 -d \
| jq -r '.connections | length')
[ "${PERSISTED:-0}" -gt 0 ] && echo "OK: connections[] persisted." || { echo "FAIL: bind missing (or getDefinition returned a 202 LRO body — see note above)." >&2; exit 1; }
# Step 7 (optional): Validate the M evaluates — top-level QueryName, PascalCase.
EQ_BODY=$(mktemp --suffix=.json 2>/dev/null || mktemp)
printf '{"QueryName":"Titanic"}' > "$EQ_BODY"
az rest --method post --resource "$RES" \
--url "$API/workspaces/$WS_ID/dataflows/$DF_ID/executeQuery" \
--headers "Content-Type=application/json" \
--body "@$EQ_BODY" --output-file /tmp/titanic.arrow
rm -f "$EQ_BODY"
# Apache Arrow stream — embedded {"Error":"..."} means failure even on HTTP 200.
grep -q '"Error":"' /tmp/titanic.arrow && { echo "executeQuery surfaced an error." >&2; exit 1; }
# Step 8 (optional): Trigger refresh with ApplyChangesIfNeeded on first run — see Example 2.Example 2: Trigger a Refresh Job
Prompt: "Trigger a refresh on this dataflow and poll until it completes."
Agent response:
# Trigger refresh (returns 202 + Location header for polling).
# jobType MUST be "Refresh"; "Pipeline" returns 400 InvalidJobType.
# On the first refresh after any updateDefinition, body MUST include executeOption=ApplyChangesIfNeeded
# (otherwise Fabric refreshes the previously-applied definition).
# Acquire $TOKEN per common/COMMON-CLI.md § Token-in-Variable Pattern (resource = https://api.fabric.microsoft.com).
LOCATION=$(curl -sS -X POST \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
--data '{"executionData":{"executeOption":"ApplyChangesIfNeeded"}}' \
"https://api.fabric.microsoft.com/v1/workspaces/${WS_ID}/dataflows/${DF_ID}/jobs/instances?jobType=Refresh" \
-o /dev/null -D - | tr -d '\r' | grep -i "^location:" | awk '{print $2}')
# Poll while the status is non-terminal. Fabric refresh job status enum:
# NotStarted / InProgress -> non-terminal, keep polling
# Completed -> success
# Failed / Cancelled -> terminal backend outcome (fatal-stop)
# Deduped -> another refresh is already running; this trigger was skipped (NOT success)
# Treat ONLY NotStarted/InProgress as non-terminal and break on anything else, so a newly-added
# terminal status surfaces immediately instead of waiting out MAX_POLLS (the contract notes more
# status values may be added over time). MAX_POLLS bounds the wait if the job never terminates.
ATTEMPT=0; MAX_POLLS="${MAX_POLLS:-60}"
while [ "$ATTEMPT" -lt "$MAX_POLLS" ]; do
STATUS=$(az rest --method get --url "$LOCATION" \
--resource "https://api.fabric.microsoft.com" --query "status" -o tsv)
echo "Status: $STATUS"
case "$STATUS" in NotStarted|InProgress) ;; *) break ;; esac
sleep 10; ATTEMPT=$((ATTEMPT + 1))
done
case "$STATUS" in
Completed) : ;; # success (exit 0)
Deduped)
# Concurrency, not success: another refresh is already running and this trigger was skipped.
# Monitor the in-flight instance instead of re-triggering. Exit non-zero so automation does not
# mistake a skipped trigger for a completed refresh.
echo "Refresh deduplicated — another instance is already running; monitor that instance instead of re-triggering."
exit 2 ;;
NotStarted|InProgress)
# Max-poll bound reached before any terminal status — a polling timeout, NOT a terminal outcome.
# Surface the raw job instance and stop; do not assume success.
echo "Polling stopped after ${MAX_POLLS} attempts with non-terminal status '$STATUS' (max-poll timeout, not a terminal outcome)."
az rest --method get --url "$LOCATION" --resource "https://api.fabric.microsoft.com"
exit 1 ;;
*) # Failed / Cancelled (or any other terminal status): a terminal backend outcome — not something
# to debug-loop. Surface the job's raw error (the job-instance body's .failureReason, an
# ErrorResponse with .errorCode / .isRetriable / .message) and STOP. Do NOT re-trigger or keep
# polling when .failureReason.isRetriable=false or the error is workspace-wide.
az rest --method get --url "$LOCATION" --resource "https://api.fabric.microsoft.com"
exit 1 ;;
esacPowerShell variant (Invoke-WebRequest exposes response headers natively; avoids the tr | grep | awk pipe):
# Notes:
# - $Resp.Headers["Location"] returns string or string[] depending on PS version — never
# use .Location[0] (returns first character on Windows PS 5.1 plain-string case).
# - Wrap Invoke-WebRequest in try/catch on 5.1 (-SkipHttpErrorCheck is PS 7+).
# - Fabric refresh job status enum: NotStarted / InProgress (non-terminal); Completed (success);
# Failed / Cancelled (fatal); Deduped (another refresh already running — NOT success). Treat only
# NotStarted/InProgress as non-terminal and break on anything else, bounded by a max-poll count, so
# a newly-added terminal status surfaces immediately instead of waiting out $MaxPolls.
# This is distinct from the LRO operation enum (Running / Succeeded / Failed / Cancelled).
# Refresh "success" = "Completed", not "Succeeded".
# Acquire $Token per common/COMMON-CLI.md § Token-in-Variable Pattern (resource = https://api.fabric.microsoft.com).
try {
$Resp = Invoke-WebRequest -Method POST -UseBasicParsing `
-Uri "https://api.fabric.microsoft.com/v1/workspaces/$WS_ID/dataflows/$DF_ID/jobs/instances?jobType=Refresh" `
-Headers @{ Authorization = "Bearer $Token"; "Content-Type" = "application/json" } `
-Body '{"executionData":{"executeOption":"ApplyChangesIfNeeded"}}'
} catch {
Write-Error "Refresh trigger failed: $($_.Exception.Message)"; exit 1
}
$Location = $Resp.Headers["Location"]
if ($Location -is [array]) { $Location = $Location[0] }
$Attempt = 0; $MaxPolls = 60
do {
$Status = az rest --method get --url $Location `
--resource "https://api.fabric.microsoft.com" --query "status" -o tsv
Write-Host "Status: $Status"
if ($Status -notin 'NotStarted','InProgress') { break }
Start-Sleep -Seconds 10; $Attempt++
} while ($Attempt -lt $MaxPolls)
if ($Status -in 'NotStarted','InProgress') {
# Max-poll bound reached before any terminal status — a polling timeout, NOT a terminal outcome.
Write-Host "Polling stopped after $MaxPolls attempts with non-terminal status '$Status' (max-poll timeout, not a terminal outcome)."
az rest --method get --url $Location --resource "https://api.fabric.microsoft.com"
exit 1
}
switch ($Status) {
'Completed' { } # success (exit 0)
'Deduped' {
# Concurrency, not success: another refresh is already running and this trigger was skipped.
# Exit non-zero so callers don't treat a skipped trigger as a completed refresh.
Write-Host "Refresh deduplicated — another instance is already running; monitor that instance instead of re-triggering."
exit 2
}
default {
# Failed / Cancelled (or any other terminal status): a terminal backend outcome — surface the job's
# raw error (the job-instance body's .failureReason: .errorCode / .isRetriable / .message) and STOP;
# do not re-trigger or debug-loop when failureReason.isRetriable=false or the error is workspace-wide.
az rest --method get --url $Location --resource "https://api.fabric.microsoft.com"
Write-Error "Refresh terminated '$Status' (not Completed)"; exit 1
}
}Example 3: Modify an Existing Dataflow's Definition
Prompt: "Update the mashup of an existing dataflow with a modified query."
Agent response — read-modify-write loop. getDefinition returns sync 200 in the typical case; this template handles the 202 + LRO branch as well.
RESOURCE="https://api.fabric.microsoft.com"
# Acquire $TOKEN per common/COMMON-CLI.md § Token-in-Variable Pattern (resource = $RESOURCE).
# 1. Read current definition (sync 200 or 202 LRO — handle both).
HDR=$(mktemp); BODY=$(mktemp)
CODE=$(curl -sS -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Length: 0" \
"$RESOURCE/v1/workspaces/${WS_ID}/dataflows/${DF_ID}/getDefinition" \
-D "$HDR" -o "$BODY" -w "%{http_code}")
if [ "$CODE" = "202" ]; then
LOC=$(tr -d '\r' < "$HDR" | grep -i "^location:" | awk '{print $2}')
RETRY=$(tr -d '\r' < "$HDR" | grep -i "^retry-after:" | awk '{print $2}'); RETRY=${RETRY:-5}
while :; do
sleep "$RETRY"
OP=$(az rest --method get --resource "$RESOURCE" --url "$LOC")
case "$(echo "$OP" | jq -r '.status // empty')" in
Succeeded) DEF=$(az rest --method get --resource "$RESOURCE" --url "${LOC%/}/result"); break ;;
Failed|Cancelled) echo "ERROR: getDefinition $(echo "$OP" | jq -r '.status')" >&2; exit 1 ;;
esac
done
else
DEF=$(cat "$BODY")
fi
rm -f "$HDR" "$BODY"
# 2. Decode each part, modify mashup.pq, re-encode all 3.
MASHUP=$(echo "$DEF" | jq -r '.definition.parts[] | select(.path=="mashup.pq") | .payload' | base64 -d)
META=$( echo "$DEF" | jq -r '.definition.parts[] | select(.path=="queryMetadata.json") | .payload' | base64 -d)
PLAT=$( echo "$DEF" | jq -r '.definition.parts[] | select(.path==".platform") | .payload' | base64 -d)
NEW_MASHUP=$(echo "$MASHUP" | sed 's/old-pattern/new-pattern/') # edit M here
MASHUP_B64=$(echo -n "$NEW_MASHUP" | base64 -w0)
META_B64=$(echo -n "$META" | base64 -w0)
PLAT_B64=$(echo -n "$PLAT" | base64 -w0)
# 3. Build the updateDefinition body in a temp file (full replacement — all 3 parts).
BODY_FILE=$(mktemp --suffix=.json 2>/dev/null || mktemp) # GNU + BSD/macOS compatible
cat > "$BODY_FILE" <<EOF
{"definition":{"parts":[
{"path":"mashup.pq", "payload":"${MASHUP_B64}", "payloadType":"InlineBase64"},
{"path":"queryMetadata.json", "payload":"${META_B64}", "payloadType":"InlineBase64"},
{"path":".platform", "payload":"${PLAT_B64}", "payloadType":"InlineBase64"}
]}}
EOF
az rest --method post --resource "$RESOURCE" \
--url "$RESOURCE/v1/workspaces/${WS_ID}/dataflows/${DF_ID}/updateDefinition?updateMetadata=true" \
--headers "Content-Type=application/json" --body "@$BODY_FILE"
rm -f "$BODY_FILE"Binding a new connection? Example 1 (steps 1-5) is the canonical bind+save flow. Bind-only walk-throughs live in authoring-cli-quickref.md § Connection Binding Quick Patterns and authoring-script-templates.md § Connection Binding Templates.
Output Expectations
When this skill completes a task, the agent should return:
| Field | Convention |
|---|---|
| Verbosity | Concise summary (3–10 lines) of what was created/modified. |
| Default format | Markdown for status reports; fenced JSON code block for single-resource responses; markdown table for list responses. |
| Side-effect disclosure | Explicitly report IDs created/modified/deleted and the target workspace ID. Never imply success without an ID. When you saved or replaced a dataflow definition, name the parts you wrote in prose -- mashup.pq, queryMetadata.json, .platform -- since long command bodies are truncated in the transcript and the part names would otherwise be lost. Also name the actual persist path you used: updateDefinition when you saved the definition through that endpoint, or the create call POST /v1/workspaces/{ws}/dataflows when the definition was supplied at create time. Do not claim updateDefinition if the definition was persisted via the create POST. |
| Verification | Re-GET the affected resource (dataflow, connection, job instance) and surface its state (e.g., provisionState, status, Completed) before declaring done. |
| Error surfacing | If any step returned a non-2xx status, an LRO Failed/Cancelled, or an Arrow-stream {"Error":"..."}, propagate the raw error verbatim and stop. A terminal refresh Failed/Cancelled, an isRetriable: false backend error, or a workspace-wide UnknownException is a fatal-stop condition — report it and end; do not re-trigger, re-poll, or enter an extended debugging loop. |
| Preview rendering (Workflow C) | After executeQuery, render head(10) of the result as a markdown table in chat alongside the saved Arrow file — even when the embedded-error check passes. Catches silent-success bugs (filter dropped all rows, wrong column, off-by-one, wrong cast) that the embedded-error detector cannot see. Snippet + suppression rules: dataflows-cli consumption mode § Example 5b. |
| API names | When the answer references API endpoints or request-body fields, use their exact, case-sensitive names (executeQuery, customMashupDocument, QueryName, mashup.pq, queryMetadata.json, GET /v1/connections/supportedConnectionTypes) rather than paraphrased or pluralized variants. |
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 Python
Security
What our scanner noticed· Low risk
- Instructions that use the network
- Scripts that your agent may run
This skill includes scripts that your agent may run. Read them before you use it.
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/dataflows-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.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download dataflows-cli.zip