Update Check — ONCE PER SESSION (mandatory)
The first time this skill is used in a session, run the check-updates skill before proceeding.
- GitHub Copilot CLI / VS Code: invoke the
check-updates skill.
- Claude Code / Cowork / Cursor / Windsurf / Codex: compare local vs remote package.json version.
- Skip if the check was already performed earlier in this session.
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
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-authoring-cli — Dataflows Gen2 Authoring via CLI
Table of Contents
This skill (SKILL.md)
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-consumption-cli |
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, and curl are available before first operation. uuidgen is 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 tsv
Reusable 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
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.type and the target URL/host — reuse if a match exists (GET /v1/connections + JMESPath).
- If no match, create the connection. First
GET /v1/connections/supportedConnectionTypes to discover required parameters and supported credential types, then POST /v1/connections (sync 201). Body shape and credential schemas: connection-management.md.
- Resolve
ClusterId for the composite binding. GET https://api.powerbi.com/v2.0/myorg/me/gatewayClusterDatasources with --query "value[?id=='$CONN_ID'] | [0].clusterId", audience --resource "https://analysis.windows.net/powerbi/api" (no trailing slash). The per-id route returns PowerBIEntityNotFound for 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}/dataflows with {"displayName":"<displayName>"} returns sync 201. The definition field is optional at create time and can be set in the next step. If you instead supply the full definition (all three parts) in this create POST, that call is the persist surface -- name POST /v1/workspaces/{ws}/dataflows (not updateDefinition) as the persist path in your summary.
- Save M + connection bindings in one call.
POST /v1/workspaces/{ws}/dataflows/{df}/updateDefinition?updateMetadata=true with three parts: mashup.pq (real Web.Contents / Sql.Database / …), queryMetadata.json (with connections[] populated; each connectionId is the stringified composite {"ClusterId":"…","DatasourceId":"…"}), and .platform. Typically returns sync 200; may return 202 + LRO Location on large bodies — handle both.
- Verify the binding persisted. Re-call
getDefinition, decode queryMetadata.json, and confirm connections[] is intact. Do not use GET /items/{id}/connections for 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 v2 gatewayClusterDatasources returns 401/403/Unauthorized or no ClusterId is visible after retries, do not stop before creating the dataflow. Create the shell, persist the saved query definitions without claiming a valid source binding, call executeQuery with QueryName only 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 so executeQuery can 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, call executeQuery for each entity, parse the Arrow IPC stream, render line charts (time-series) or horizontal bar charts (categories) via references/charts/line_chart.py / references/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=Refresh with body {"executionData":{"executeOption":"ApplyChangesIfNeeded"}}. ApplyChangesIfNeeded is required on the first refresh after any definition change — without it, Fabric refreshes the previously-applied definition. Poll the LRO until status is Completed (refresh enum) or Failed/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 2
One-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 updateDefinition followed by a full-M updateDefinition. 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 (decode mashup.pq and queryMetadata.json). Validate all connections[] entries via GET /v1/connections/{id}.
- Formulate — modify M, re-encode parts, ensure every referenced
connectionId exists in the caller's connection store.
- Execute —
POST .../updateDefinition?updateMetadata=true with all 3 parts (full replacement). Optionally trigger refresh.
- Verify — re-call
getDefinition to confirm changes; poll refresh LRO; for refresh failures, make at most one executeQuery isolation attempt to localize a fixable M/source issue. On a terminal/non-retriable failure (isRetriable: false, workspace-wide UnknownException), 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 (QueryName only) or run an ad-hoc read-only customMashupDocument with no intent to persist, use dataflows-consumption-cli. mashup-preview.md is 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": false on the destination query — refresh fails without it. State this in your summary using the literal part name (e.g., "set loadEnabled: false on the destination query in queryMetadata.json").
- Always use
IsNewTarget = true for 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 composite ClusterId/DatasourceId ID.
- First refresh must use
ApplyChangesIfNeeded to 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., "Saved mashup.pq + queryMetadata.json + .platform via updateDefinition"). Do not abbreviate queryMetadata.json to "query metadata" or the inner field queriesMetadata.
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 persisting updateDefinition. 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 login first — all az rest calls 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 fails AADSTS500011 invalid_resource.
- Base64-encode all 3 definition parts —
mashup.pq + queryMetadata.json + .platform, each payloadType: "InlineBase64". updateDefinition is a full replacement; sending 1 or 2 parts silently drops queries.
- Handle sync AND async responses.
POST /dataflows, updateDefinition, and getDefinition typically return sync (200/201) but may return 202 + LRO Location on large bodies — handle both. See authoring-script-templates.md § Fabric LRO Polling Pattern.
- Set
formatVersion: "202502" in queryMetadata.json and include a top-level name matching displayName — omitting either causes save-time failures or stale display-name state.
loadEnabled is opt-out, not opt-in. Fabric auto-loads every query to the staging Lakehouse by default; set loadEnabled: false only on helper queries you do not want written. Note: loadEnabled: true is also stripped from queryMetadata.json on round-trip via getDefinition (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/connections operations take the plain GUID from connection.id; queryMetadata.json connections[].connectionId takes the stringified composite {"ClusterId":"…","DatasourceId":"…"}. See connection-management.md § Connection ID Format Cheat Sheet.
- Resolve
ClusterId via list+filter. GET .../gatewayClusterDatasources filtered by value[?id=='$CONN_ID']. The per-id route returns PowerBIEntityNotFound for cloud connections; newly-created connections may need a 5-15 s retry. See connection-management.md § Resolving ClusterId.
executeQuery body uses a top-level QueryName field (PascalCase canonical; the field name itself is case-insensitive on the wire — lowercase queryName also evaluates). Value must name a shared member from the persisted M or the supplied customMashupDocument. The {"queries":[…]} array shape always fails with DataflowExecuteQueryError: Invalid query name; a wrong query name returns QueryNotFound. Full contract: mashup-preview.md § Request body.
- Use the exact, case-sensitive API names. The endpoint is
executeQuery (singular, never executeQueries) and the request-body field is customMashupDocument (never mashupDocument, never base64-encoded — it is a plain UTF-8 M string). The same M body becomes the saved mashup.pq part referenced as customMashupDocument. Vocabulary table: mashup-preview.md § Vocabulary.
- First refresh after any
updateDefinition MUST use executeOption: "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 carries isRetriable: false (or a workspace-wide UnknownException), 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 one executeQuery isolation 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/supportedConnectionTypes before POST /v1/connections -- never guess parameter names or credential types; they vary by connector, tenant, and time. When summarizing a connector's required parameters or credentialType set for a user, use the exact, case-sensitive endpoint path GET /v1/connections/supportedConnectionTypes. This applies even to a pure lookup ("which parameters/credentialType does connector X support?"): run the live GET /v1/connections/supportedConnectionTypes against the tenant. The bundled connection-management.md reference 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
connectionId in queryMetadata.json, GET /v1/connections/{id} (plain GUID extracted from the composite). Cryptic EntityUserFailure at 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 to executeQuery; persist it through at least one updateDefinition first. Detail: mashup-preview.md § Bootstrap branch.
- Send a full
section Section1; ... document in customMashupDocument — executeQuery does not auto-wrap raw expressions. See mashup-preview.md § customMashupDocument format.
- Preview candidate M via
executeQuery before updateDefinition — 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 because cmd.exe's argument parser mangles embedded quotes. See authoring-script-templates.md § PowerShell — Create Dataflow with Definition.
- Prefer
WorkspaceIdentity / ServicePrincipal credentials for unattended refresh. OAuth2 + singleSignOnType: None works for interactive executeQuery but is fragile under tenant Conditional Access for service-context refresh. Check supported types via supportedConnectionTypes.
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
format property to definition — Items API uses parts[] only; "format": "json" returns 400 InvalidDefinitionFormat.
- Hardcoded workspace/dataflow GUIDs — discover via REST API (Connection section).
- Using
GET /v1/workspaces/{ws}/items/{itemId}/connections to verify a freshly-bound dataflow. It reflects refresh-materialized state, not the persisted definition, and returns 0 after a successful bind. Verify via getDefinition + decode queryMetadata.json.connections[].
- Assuming
updateDefinition / POST /dataflows is 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/") — fails AADSTS500011 invalid_resource. Use the no-slash form.
- Per-id
gatewayClusterDatasources/{id} for cloud connections — returns PowerBIEntityNotFound. Use list+filter (MUST DO above).
{"queries":[…]} array body shape for executeQuery — always returns 400 DataflowExecuteQueryError: Invalid query name regardless of inner casing. Use a top-level QueryName (or queryName — the field is case-insensitive); pick exactly one query per call.
- Using
GET for getDefinition — it's a POST endpoint; GET returns 405.
- Constructing operation URLs manually — always follow the
Location header from a 202 response.
- Duplicate
displayName values — 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 empty GET /v1/connections is not proof a connection is absent.
- Hand-crafting connection request bodies without
supportedConnectionTypes — guessing produces 400 InvalidConnectionDetails / 400 InvalidCredentialDetails.
- Plaintext credentials in generated examples or committed scripts — never render or commit plaintext credential values. Show only
passwordReference / keyReference / tokenReference / servicePrincipalSecretReference in generated connection bodies.
- Templating on-prem gateway connection bodies as plaintext —
OnPremisesGateway needs 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 — executeQuery is significantly faster than the updateDefinition-then-debug-refresh loop. See mashup-preview.md.
- Unbounded preview against production-volume sources —
executeQuery returns the full evaluated dataset. Inject Table.FirstN / TOP N / date predicate into the preview-only document; strip before saving. See mashup-preview.md § Hard avoid.
- Confusing
executeQuery with EvaluateQuery. EvaluateQuery requires a prior successful refresh; executeQuery + customMashupDocument does not. Use executeQuery for the authoring preview loop.
- Inline
--body on Windows/PowerShell — cmd.exe mangles quotes; always use --body "@$env:TEMP\<name>.json".
PREFER
- One-shot
updateDefinition carrying 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 rest over raw curl — handles token acquisition and refresh automatically. Fall back to curl only when you need to capture response headers (e.g., 202 LRO Location) — az rest cannot.
getDefinition before updateDefinition — read-modify-write prevents accidental data loss; updateDefinition is a full replacement.
?updateMetadata=true on updateDefinition — ensures .platform changes (display name) are applied.
jq for 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[] and GET /v1/connections/{id} in one pass before refresh; optionally POST /v1/connections/{id}/testConnection to 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)) over Out-File (which writes a UTF-8 BOM on Windows PowerShell 5.1 and breaks az.exe body parsing) and over inline --body "{...}" (which cmd.exe mangles).
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"
…(truncated)
1---2name: dataflows-authoring-cli3description: Create, update, delete, and refresh Fabric Dataflows Gen2 with write-side CLI via Fabric APIs. Build mashup.pq and queryMetadata.json, preview candidate M with executeQuery/customMashupDocument, bind connections, and configure output destinations. For saved query execution or refresh-status reads, use `dataflows-consumption-cli`. If a request explicitly insists on the Dataflows consumption or read-only path for a mutation, do not route here; let consumption refuse before any separately confirmed authoring handoff. Triggers: "create dataflow", "update dataflow", "delete dataflow", "trigger dataflow refresh", "preview Power Query M", "preview before save", "customMashupDocument", "create Fabric data source connection", "create SQL Server source REST", "POST /v1/connections", "supportedConnectionTypes", "passwordReference", "bind connection", "dataflow output destination", "dataflow write to lakehouse", "dataflow write to warehouse", "dataflow write to ADX", "DataDestinations annotation".4---56> **Update Check — ONCE PER SESSION (mandatory)**7> The first time this skill is used in a session, run the **check-updates** skill before proceeding.8> - **GitHub Copilot CLI / VS Code**: invoke the `check-updates` skill.9> - **Claude Code / Cowork / Cursor / Windsurf / Codex**: compare local vs remote package.json version.10> - Skip if the check was already performed earlier in this session.1112> **CRITICAL NOTES**13> 1. To find the workspace details (including its ID) from workspace name: list all workspaces and, then, use JMESPath filtering14> 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 filtering1516> **PRE-MUTATION REQUIREMENTS GATE (mandatory)**17> Before any mutation, confirm enough user intent for the requested operation.18> For create or update requests, establish the source, query/schema and19> transformation, destination behavior, and refresh behavior (including an20> explicit choice of no destination or no refresh). If the request is generic,21> such as "set up the Dataflow I need for reporting," ask a structured22> clarifying question and stop before mutation. Do not infer requirements from23> unrelated workspace items or create a best-guess source, connection, or24> Dataflow.2526# dataflows-authoring-cli — Dataflows Gen2 Authoring via CLI2728## Table of Contents2930**This skill (`SKILL.md`)**3132| Section | Notes |33|---|---|34| [Tool Stack](#tool-stack) | `az` + `jq` + `base64` + `curl` |35| [Connection](#connection) | Workspace/dataflow ID discovery |36| [Agentic Workflows](#agentic-workflows) | **Start here.** A: create end-to-end; B: modify existing; C: preview loop |37| [MUST DO / AVOID / PREFER](#must-do) | Authoring rules |38| [Troubleshooting](#troubleshooting) | Symptom → fix table |39| [Examples](#examples) | Runnable bash + PowerShell recipes |40| [Output Expectations](#output-expectations) | Response conventions |4142**References** (in [`references/`](references/))4344| File | When to read |45|---|---|46| [authoring-cli-quickref.md](references/authoring-cli-quickref.md) | One-liner recipes, status enums, base64 helpers, connection-binding quick patterns |47| [authoring-script-templates.md](references/authoring-script-templates.md) | Full bash + PowerShell templates; end-to-end smoke test; LRO polling pattern |48| [connection-management.md](references/connection-management.md) | List/create/inspect connections; `supportedConnectionTypes`; resolve `ClusterId`; ID format cheat sheet |49| [connectors.md](references/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 |50| [m-language.md](references/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`) |51| [mashup-preview.md](references/mashup-preview.md) | `executeQuery` contract: bootstrap branch, auto-wrap rule, hard avoid for unbounded preview |52| [output-destinations.md](references/output-destinations.md) | Output destination patterns: Lakehouse Table, Lakehouse Files, Warehouse, ADX, Azure SQL. `DataDestinations` annotation, hidden query, `loadEnabled` rules, connection limitations |5354**Common refs** (in [`../../common/`](../../common/))5556| File | When to read |57|---|---|58| [COMMON-CLI.md](../../common/COMMON-CLI.md) | `az login`, token acquisition, `az rest`, pagination, LRO polling, CLI gotchas. **§ Finding Workspaces and Items in Fabric is mandatory.** |59| [COMMON-CORE.md](../../common/COMMON-CORE.md) | Fabric topology, environment URLs, authentication, core REST API surface |60| [ITEM-DEFINITIONS-CORE.md](../../common/ITEM-DEFINITIONS-CORE.md) | Definition envelope; per-item-type payload contracts |61| [DATAFLOWS-AUTHORING-CORE.md](../../common/DATAFLOWS-AUTHORING-CORE.md) | Authoring capability matrix; 3-part definition structure; M structure; connection model; ALM / Git integration |6263**Sister skills**6465| Skill | Use for |66|---|---|67| [dataflows-consumption-cli](../dataflows-consumption-cli/SKILL.md) | Execute persisted queries; ad-hoc read-only `customMashupDocument` with no intent to persist; Arrow → CSV/pandas conversion; refresh status/history. |6869---7071## Tool Stack7273| Tool | Role | Install |74|---|---|---|75| `az` CLI | **Primary**: Auth (`az login`), REST API calls (`az rest`), token acquisition. | Pre-installed in most dev environments |76| `jq` | Parse and manipulate JSON responses and definition payloads. | Pre-installed or trivial |77| `base64` | Encode/decode definition parts for the REST API. | Built into bash / `[Convert]::ToBase64String()` in PowerShell |78| `curl` | Alternative to `az rest` when raw HTTP control is needed. | Pre-installed |79| `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 |8081> **Agent check** — verify `az`, `jq`, and `curl` are available before first operation. `uuidgen` is only needed for Workflow A (Create).82> For installation and auth setup see [COMMON-CLI.md](../../common/COMMON-CLI.md).8384---8586## Connection8788### Discover Workspace and Dataflow IDs8990Per [COMMON-CLI.md](../../common/COMMON-CLI.md) Finding Workspaces and Items in Fabric:9192```bash93# List workspaces — find workspace ID by name94az rest --method get \95 --resource "https://api.fabric.microsoft.com" \96 --url "https://api.fabric.microsoft.com/v1/workspaces" \97 --query "value[?displayName=='MyWorkspace'].id" --output tsv9899# List dataflows in workspace — find dataflow ID by name100WS_ID="<workspaceId>"101az rest --method get \102 --resource "https://api.fabric.microsoft.com" \103 --url "https://api.fabric.microsoft.com/v1/workspaces/$WS_ID/dataflows" \104 --query "value[?displayName=='MyDataflow'].id" --output tsv105```106107### Reusable Connection Variables108109```bash110WS_ID="<workspaceId>"111DF_ID="<dataflowId>"112API="https://api.fabric.microsoft.com/v1"113RESOURCE="https://api.fabric.microsoft.com"114```115116---117118## Agentic Workflows119120Three workflows cover the typical authoring tasks:121122- **[A. Create a New Dataflow End-to-End](#a-create-a-new-dataflow-end-to-end)** — discover/create a connection, create the dataflow, save M + bindings, validate, optionally refresh.123- **[B. Modify an Existing Dataflow](#b-modify-an-existing-dataflow)** — read-modify-write the definition; the canonical Discover → Formulate → Execute → Verify loop.124- **[C. Preview-Driven Authoring Loop](#c-preview-driven-authoring-loop)** — iterate on candidate M via `executeQuery` before persisting via `updateDefinition`.125- **[D. Output Destination](#d-output-destination)** — write query results to Lakehouse (table/files), Warehouse, ADX, or Azure SQL via `DataDestinations` annotation. Full reference: [output-destinations.md](references/output-destinations.md).126127### A. Create a New Dataflow End-to-End128129Use 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.130131**Steps:**1321331. **List existing connections** and filter by `connectionDetails.type` and the target URL/host — reuse if a match exists (`GET /v1/connections` + JMESPath).1342. **If no match, create the connection.** First `GET /v1/connections/supportedConnectionTypes` to discover required parameters and supported credential types, then `POST /v1/connections` (sync 201). Body shape and credential schemas: [connection-management.md](references/connection-management.md).1353. **Resolve `ClusterId` for the composite binding.** `GET https://api.powerbi.com/v2.0/myorg/me/gatewayClusterDatasources` with `--query "value[?id=='$CONN_ID'] | [0].clusterId"`, audience `--resource "https://analysis.windows.net/powerbi/api"` (no trailing slash). The per-id route returns `PowerBIEntityNotFound` for cloud connections. Newly-created connections may take a few seconds to surface — retry on empty. Detail: [connection-management.md § Resolving ClusterId](references/connection-management.md#resolving-clusterid-power-bi-v2).1364. **Create the dataflow shell.** `POST /v1/workspaces/{ws}/dataflows` with `{"displayName":"<displayName>"}` returns sync 201. The `definition` field is optional at create time and can be set in the next step. If you instead supply the full `definition` (all three parts) in this create POST, that call is the persist surface -- name `POST /v1/workspaces/{ws}/dataflows` (not `updateDefinition`) as the persist path in your summary.1375. **Save M + connection bindings in one call.** `POST /v1/workspaces/{ws}/dataflows/{df}/updateDefinition?updateMetadata=true` with three parts: `mashup.pq` (real `Web.Contents` / `Sql.Database` / …), `queryMetadata.json` (with `connections[]` populated; each `connectionId` is the stringified composite `{"ClusterId":"…","DatasourceId":"…"}`), and `.platform`. Typically returns sync 200; may return 202 + LRO `Location` on large bodies — handle both.1386. **Verify the binding persisted.** Re-call `getDefinition`, decode `queryMetadata.json`, and confirm `connections[]` is intact. **Do not** use `GET /items/{id}/connections` for verification — that endpoint reflects refresh-materialized state, not the persisted definition, and returns 0 even after a successful bind. See [AVOID](#avoid).139 **Guarded preview-only fallback:** If connection binding cannot proceed because Power BI v2 `gatewayClusterDatasources` returns 401/403/Unauthorized or no `ClusterId` is visible after retries, do not stop before creating the dataflow. Create the shell, persist the saved query definitions without claiming a valid source binding, call `executeQuery` with `QueryName` only for each saved query, surface the exact credential or binding error from the Arrow stream, do not fabricate chart data, and do not refresh.1407. **(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 so `executeQuery` can see it — use the pre-persist [Preview-Driven Authoring Loop](#c-preview-driven-authoring-loop); the chart rendering is identical, only the ordering relative to the save differs.) If accepted, call `executeQuery` for each entity, parse the Arrow IPC stream, render line charts (time-series) or horizontal bar charts (categories) via `references/charts/line_chart.py` / `references/charts/bar_chart.py`, and ask the user to confirm before proceeding. Details: [mashup-preview.md § ASCII chart preview](references/mashup-preview.md#ascii-chart-preview-optional). If declined, proceed directly to step 8.1418. **(Optional) Trigger refresh** to materialize. `POST .../jobs/instances?jobType=Refresh` with body `{"executionData":{"executeOption":"ApplyChangesIfNeeded"}}`. **`ApplyChangesIfNeeded` is required on the first refresh after any definition change** — without it, Fabric refreshes the previously-applied definition. Poll the LRO until `status` is `Completed` (refresh enum) or `Failed`/`Cancelled`.142143```bash144# Concise skeleton — full runnable bash is Example 1 below.145# PowerShell + LRO-polled variants: references/authoring-script-templates.md146147WS_ID="<workspaceId>"; URL="<source-url>"148RES="https://api.fabric.microsoft.com"; API="$RES/v1"149PBI="https://analysis.windows.net/powerbi/api"150151# 1. List existing & try reuse152CONN_ID=$(az rest --method get --resource "$RES" --url "$API/connections" \153 --query "value[?connectionDetails.type=='Web' && connectionDetails.path=='$URL'] | [0].id" -o tsv)154155# 2. Create connection if missing — see connection-management.md for full body156# 3. List+filter for ClusterId157CLUSTER_ID=$(az rest --method get --resource "$PBI" \158 --url "https://api.powerbi.com/v2.0/myorg/me/gatewayClusterDatasources" \159 --query "value[?id=='$CONN_ID'] | [0].clusterId" -o tsv)160161# 4. Empty dataflow shell — sync 201162SHELL_BODY=$(mktemp --suffix=.json 2>/dev/null || mktemp)163printf '{"displayName":"my-df"}' > "$SHELL_BODY"164DF_ID=$(az rest --method post --resource "$RES" \165 --url "$API/workspaces/$WS_ID/dataflows" \166 --headers "Content-Type=application/json" \167 --body "@$SHELL_BODY" --query id -o tsv)168rm -f "$SHELL_BODY"169170# 5. One-shot updateDefinition with real M + connections[] (sync 200 typical)171# Body assembly (mashup.pq + queryMetadata.json + .platform, base64-encoded;172# queryMetadata.json.connections[].connectionId = composite ClusterId/DatasourceId):173# see Example 1 below.174175# 6. Verify via getDefinition (NOT GET /items/{id}/connections — see AVOID)176# 7. (optional) executeQuery — Workflow C177# 8. (optional) Refresh with executeOption=ApplyChangesIfNeeded — Example 2178```179180> **One-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 `updateDefinition` followed by a full-M `updateDefinition`. Both work — see [PREFER](#prefer).181182### B. Modify an Existing Dataflow183184Use this when the dataflow already exists. Canonical Discover → Formulate → Execute → Verify loop. If the dataflow does not yet exist, see [Workflow A](#a-create-a-new-dataflow-end-to-end) instead.1851861. **Discover** — list workspaces, list dataflows, `getDefinition` (decode `mashup.pq` and `queryMetadata.json`). Validate all `connections[]` entries via `GET /v1/connections/{id}`.1872. **Formulate** — modify M, re-encode parts, ensure every referenced `connectionId` exists in the caller's connection store.1883. **Execute** — `POST .../updateDefinition?updateMetadata=true` with **all 3 parts** (full replacement). Optionally trigger refresh.1894. **Verify** — re-call `getDefinition` to confirm changes; poll refresh LRO; for refresh failures, make at most **one** `executeQuery` isolation attempt to localize a fixable M/source issue. On a terminal/non-retriable failure (`isRetriable: false`, workspace-wide `UnknownException`), surface the raw error and **stop** rather than re-triggering.190191```bash192# Concise skeleton — full templates: references/authoring-script-templates.md193# Acquire $TOKEN per common/COMMON-CLI.md § Token-in-Variable Pattern (resource = $RESOURCE).194RESOURCE="https://api.fabric.microsoft.com"; API="$RESOURCE/v1"195196# 1. Discover — getDefinition (handles 200 sync and 202 + LRO via curl)197HDR=$(mktemp); BODY=$(mktemp)198CODE=$(curl -sS -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Length: 0" \199 "$API/workspaces/$WS_ID/dataflows/$DF_ID/getDefinition" \200 -D "$HDR" -o "$BODY" -w "%{http_code}")201if [ "$CODE" = "202" ]; then202 LOC=$(tr -d '\r' < "$HDR" | grep -i "^location:" | awk '{print $2}')203 RETRY=$(tr -d '\r' < "$HDR" | grep -i "^retry-after:" | awk '{print $2}'); RETRY=${RETRY:-5}204 while :; do205 sleep "$RETRY"206 OP=$(az rest --method get --resource "$RESOURCE" --url "$LOC")207 case "$(echo "$OP" | jq -r '.status // empty')" in208 Succeeded) RESULT=$(az rest --method get --resource "$RESOURCE" --url "${LOC%/}/result"); break ;;209 Failed|Cancelled) echo "ERROR: getDefinition $(echo "$OP" | jq -r '.status')" >&2; exit 1 ;;210 esac211 done212else213 RESULT=$(cat "$BODY")214fi215rm -f "$HDR" "$BODY"216217# Validate bound connections (connectionId is a composite JSON string — iterate safely)218QUERY_META=$(echo "$RESULT" | jq -r '.definition.parts[] | select(.path=="queryMetadata.json") | .payload' | base64 -d)219echo "$QUERY_META" | jq -c '.connections[]?' | while IFS= read -r conn; do220 RAW=$(echo "$conn" | jq -r '.connectionId')221 DATASOURCE_ID=$(echo "$RAW" | jq -r '.DatasourceId? // empty' 2>/dev/null)222 [ -z "$DATASOURCE_ID" ] && DATASOURCE_ID="$RAW"223 # GET /v1/connections/$DATASOURCE_ID to confirm access224done225226# 2-3. Formulate & Execute — see Example 3227# 4. Verify — trigger refresh via curl (az rest cannot capture Location header).228# Full LRO polling: references/authoring-script-templates.md.229```230231### C. Preview-Driven Authoring Loop (pre-save executeQuery — see [mashup-preview.md](references/mashup-preview.md#preview-driven-authoring-loop))232233When 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](references/mashup-preview.md#preview-driven-authoring-loop).234235> **Intent split.** This workflow is for the *pre-save* intent. To execute a **saved** query (`QueryName` only) or run an **ad-hoc read-only** `customMashupDocument` with no intent to persist, use [`dataflows-consumption-cli`](../dataflows-consumption-cli/SKILL.md#query-evaluation). `mashup-preview.md` is the shared API reference for both intents.236237Skip 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).238239### D. Output Destination240241Use 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](references/output-destinations.md).242243**Key requirements:**2442451. **Source query** carries a `[DataDestinations = {[...]}]` annotation referencing the destination query by name.2462. **Hidden destination query** (suffixed `_DataDestination`) navigates to the target storage using null-safe `?[Data]?` (tables) or `?[Content]?` (files) operators.2473. **queryMetadata.json** must set `"loadEnabled": false` on the destination query — refresh fails without it. State this in your summary using the literal part name (e.g., "set `loadEnabled: false` on the destination query in `queryMetadata.json`").2484. **Always use `IsNewTarget = true`** for API-created dataflows, even for existing tables.2495. **Bind the appropriate connection** (Lakehouse: kind `"Lakehouse"`; Warehouse: kind `"Warehouse"`; ADX: kind `"AzureDataExplorer"`; Azure SQL: kind `"Sql"`) with composite `ClusterId`/`DatasourceId` ID.2506. **First refresh must use `ApplyChangesIfNeeded`** to publish the draft and reconcile annotations.2517. **All source columns must be typed** — `Any`-type columns are rejected by all destination types.2528. **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., "Saved `mashup.pq` + `queryMetadata.json` + `.platform` via `updateDefinition`"). Do not abbreviate `queryMetadata.json` to "query metadata" or the inner field `queriesMetadata`.253254**Supported destinations:**255256| Destination | Connection Kind | Destination Query Function | Notes |257|---|---|---|---|258| Lakehouse Table | `Lakehouse` | `Lakehouse.Contents(...)` | Path: `"Lakehouse"` |259| Lakehouse Files | `Lakehouse` | `Lakehouse.Contents(...)` | `TypeSettings = [Kind = "File"]`, `?[Content]?` |260| Warehouse | `Warehouse` | `Fabric.Warehouse(...)` | Path: `"Warehouse"`, Schema/Item navigation |261| Azure Data Explorer | `AzureDataExplorer` | `AzureDataExplorer.Contents(...)` | Path must match connection exactly (trailing slash!) |262| Azure SQL | `Sql` | `Sql.Database(...)` | Path: `"server;database"` |263264**Minimal steps:** Create dataflow → Find/create connection → Resolve ClusterId → Save definition with OD annotations → Verify → Refresh.265266```bash267# Skeleton — full PowerShell recipe: references/output-destinations.md § Complete Example268WS_ID="<workspaceId>"; LH_ID="<lakehouseId>"; RES="https://api.fabric.microsoft.com"269270# M pattern (two queries):271# 1. Source with [DataDestinations] annotation272# 2. Hidden _DataDestination query with ?[Data]? null-safe navigation273# queryMetadata: source loadEnabled=true, destination loadEnabled=false + isHidden=true274# Refresh: {"executionData":{"executeOption":"ApplyChangesIfNeeded"}}275```276277---278279## Gotchas, Rules, Troubleshooting280281For full authoring gotchas: [DATAFLOWS-AUTHORING-CORE.md](../../common/DATAFLOWS-AUTHORING-CORE.md) Gotchas and Troubleshooting.282For CLI-specific issues: [COMMON-CLI.md](../../common/COMMON-CLI.md) Gotchas & Troubleshooting (CLI-Specific).283For connection discovery: [authoring-cli-quickref.md § Connection Discovery and Validation](references/authoring-cli-quickref.md#connection-discovery-and-validation).284285### MUST DO286287- **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](#c-preview-driven-authoring-loop) the preview instead precedes the persisting `updateDefinition`. If accepted, follow [mashup-preview.md § ASCII chart preview](references/mashup-preview.md#ascii-chart-preview-optional). Skip only for metadata-only edits (display name, schedule) or when the agent records an explicit skip reason.288- **`az login` first** — all `az rest` calls use the active session. No session → 401.289- **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 fails `AADSTS500011 invalid_resource`.290- **Base64-encode all 3 definition parts** — `mashup.pq` + `queryMetadata.json` + `.platform`, each `payloadType: "InlineBase64"`. `updateDefinition` is a full replacement; sending 1 or 2 parts silently drops queries.291- **Handle sync AND async responses.** `POST /dataflows`, `updateDefinition`, and `getDefinition` typically return sync (200/201) but may return 202 + LRO `Location` on large bodies — handle both. See [authoring-script-templates.md § Fabric LRO Polling Pattern](references/authoring-script-templates.md#fabric-lro-polling-pattern).292- **Set `formatVersion: "202502"`** in `queryMetadata.json` and include a top-level `name` matching `displayName` — omitting either causes save-time failures or stale display-name state.293- **`loadEnabled` is opt-out, not opt-in.** Fabric auto-loads every query to the staging Lakehouse by default; set `loadEnabled: false` only on helper queries you do not want written. Note: `loadEnabled: true` is also stripped from `queryMetadata.json` on round-trip via `getDefinition` (it's the default) — its absence on read-back is **not** a bug. Detail: [DATAFLOWS-AUTHORING-CORE.md § loadEnabled semantics](../../common/DATAFLOWS-AUTHORING-CORE.md).294- **Use the right ID format per context.** REST `/v1/connections` operations take the **plain GUID** from `connection.id`; `queryMetadata.json connections[].connectionId` takes the **stringified composite** `{"ClusterId":"…","DatasourceId":"…"}`. See [connection-management.md § Connection ID Format Cheat Sheet](references/connection-management.md#connection-id-format-cheat-sheet).295- **Resolve `ClusterId` via list+filter.** `GET .../gatewayClusterDatasources` filtered by `value[?id=='$CONN_ID']`. The per-id route returns `PowerBIEntityNotFound` for cloud connections; newly-created connections may need a 5-15 s retry. See [connection-management.md § Resolving ClusterId](references/connection-management.md#resolving-clusterid-power-bi-v2).296- **`executeQuery` body uses a top-level `QueryName` field** (PascalCase canonical; the field name itself is case-insensitive on the wire — lowercase `queryName` also evaluates). Value must name a `shared` member from the persisted M or the supplied `customMashupDocument`. The `{"queries":[…]}` array shape **always** fails with `DataflowExecuteQueryError: Invalid query name`; a wrong query name returns `QueryNotFound`. Full contract: [mashup-preview.md § Request body](references/mashup-preview.md).297- **Use the exact, case-sensitive API names.** The endpoint is `executeQuery` (singular, never `executeQueries`) and the request-body field is `customMashupDocument` (never `mashupDocument`, never base64-encoded — it is a plain UTF-8 M string). The same M body becomes the saved `mashup.pq` part referenced as `customMashupDocument`. Vocabulary table: [mashup-preview.md § Vocabulary](references/mashup-preview.md#vocabulary----name-the-things-you-send).298- **First refresh after any `updateDefinition` MUST use `executeOption: "ApplyChangesIfNeeded"`.** Body: `{"executionData":{"executeOption":"ApplyChangesIfNeeded"}}`. Without it, Fabric refreshes the previously-applied definition.299- **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 carries `isRetriable: false` (or a workspace-wide `UnknownException`), 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 **one** `executeQuery` isolation attempt to localize a fixable M/source cause; if that does not reveal a definition-side issue, end and surface the error.300- **Call `GET /v1/connections/supportedConnectionTypes` before `POST /v1/connections`** -- never guess parameter names or credential types; they vary by connector, tenant, and time. When summarizing a connector's required parameters or `credentialType` set for a user, use the exact, case-sensitive endpoint path `GET /v1/connections/supportedConnectionTypes`. This applies even to a pure lookup ("which parameters/credentialType does connector X support?"): run the live `GET /v1/connections/supportedConnectionTypes` against the tenant. The bundled `connection-management.md` reference guides the response shape but is not a substitute for the tenant-specific, case-sensitive values, which vary by connector and over time.301- **Validate referenced connections before refresh.** For each `connectionId` in `queryMetadata.json`, `GET /v1/connections/{id}` (plain GUID extracted from the composite). Cryptic `EntityUserFailure` at refresh time is often a missing/inaccessible connection. See [connection-management.md](references/connection-management.md).302- **Bootstrap-bind connections before previewing credentialed M.** A `connections[]` array in the initial create payload is **not** yet visible to `executeQuery`; persist it through at least one `updateDefinition` first. Detail: [mashup-preview.md § Bootstrap branch](references/mashup-preview.md#bootstrap-branch--new-dataflow--new-credentialed-source).303- **Send a full `section Section1; ...` document in `customMashupDocument`** — `executeQuery` does not auto-wrap raw expressions. See [mashup-preview.md § customMashupDocument format](references/mashup-preview.md#custommashupdocument-format).304- **Preview candidate M via `executeQuery` before `updateDefinition`** — 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.305- **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 because `cmd.exe`'s argument parser mangles embedded quotes. See [authoring-script-templates.md § PowerShell — Create Dataflow with Definition](references/authoring-script-templates.md#powershell--create-dataflow-with-definition).306- **Prefer `WorkspaceIdentity` / `ServicePrincipal` credentials for unattended refresh.** `OAuth2` + `singleSignOnType: None` works for interactive `executeQuery` but is fragile under tenant Conditional Access for service-context refresh. Check supported types via `supportedConnectionTypes`.307308### AVOID309310- **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.311- **Adding a `format` property to `definition`** — Items API uses `parts[]` only; `"format": "json"` returns `400 InvalidDefinitionFormat`.312- **Hardcoded workspace/dataflow GUIDs** — discover via REST API (Connection section).313- **Using `GET /v1/workspaces/{ws}/items/{itemId}/connections` to verify a freshly-bound dataflow.** It reflects refresh-materialized state, **not** the persisted definition, and returns 0 after a successful bind. Verify via `getDefinition` + decode `queryMetadata.json.connections[]`.314- **Assuming `updateDefinition` / `POST /dataflows` is always LRO.** Typical responses are sync (200/201); handle both shapes — see MUST DO above.315- **Requesting the PBI v2 token with a trailing slash** (`--resource "https://analysis.windows.net/powerbi/api/"`) — fails `AADSTS500011 invalid_resource`. Use the no-slash form.316- **Per-id `gatewayClusterDatasources/{id}` for cloud connections** — returns `PowerBIEntityNotFound`. Use list+filter (MUST DO above).317- **`{"queries":[…]}` array body shape for `executeQuery`** — always returns `400 DataflowExecuteQueryError: Invalid query name` regardless of inner casing. Use a top-level `QueryName` (or `queryName` — the field is case-insensitive); pick exactly one query per call.318- **Using `GET` for `getDefinition`** — it's a POST endpoint; `GET` returns 405.319- **Constructing operation URLs manually** — always follow the `Location` header from a 202 response.320- **Duplicate `displayName` values** — not enforced but causes confusion.321- **Binding connections by display name** — connection IDs are the source of truth; names can change.322- **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 empty `GET /v1/connections` is not proof a connection is absent.323- **Hand-crafting connection request bodies without `supportedConnectionTypes`** — guessing produces `400 InvalidConnectionDetails` / `400 InvalidCredentialDetails`.324- **Plaintext credentials in generated examples or committed scripts** — never render or commit plaintext credential values. Show only `passwordReference` / `keyReference` / `tokenReference` / `servicePrincipalSecretReference` in generated connection bodies.325- **Templating on-prem gateway connection bodies as plaintext** — `OnPremisesGateway` needs RSA-encrypted credentials per gateway member.326- **Converting a published single-source dataflow to multi-source in place** — bindings drift into inconsistent state; create fresh and retire the old.327- **Persisting un-previewed candidate M via `updateDefinition`** — `executeQuery` is significantly faster than the `updateDefinition`-then-debug-refresh loop. See [mashup-preview.md](references/mashup-preview.md).328- **Unbounded preview against production-volume sources** — `executeQuery` returns the **full** evaluated dataset. Inject `Table.FirstN` / `TOP N` / date predicate into the preview-only document; strip before saving. See [mashup-preview.md § Hard avoid](references/mashup-preview.md#hard-avoid-unbounded-production-volume-preview).329- **Confusing `executeQuery` with `EvaluateQuery`.** `EvaluateQuery` requires a prior successful refresh; `executeQuery` + `customMashupDocument` does not. Use `executeQuery` for the authoring preview loop.330- **Inline `--body` on Windows/PowerShell** — `cmd.exe` mangles quotes; always use `--body "@$env:TEMP\<name>.json"`.331332### PREFER333334- **One-shot `updateDefinition` carrying 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](references/mashup-preview.md#bootstrap-branch--new-dataflow--new-credentialed-source)).335- **`az rest` over raw `curl`** — handles token acquisition and refresh automatically. Fall back to `curl` only when you need to capture response headers (e.g., 202 LRO `Location`) — `az rest` cannot.336- **`getDefinition` before `updateDefinition`** — read-modify-write prevents accidental data loss; `updateDefinition` is a full replacement.337- **`?updateMetadata=true` on `updateDefinition`** — ensures `.platform` changes (display name) are applied.338- **`jq` for JSON manipulation** — build definition payloads programmatically.339- **`"Automatic"` for parameter type in job execution** — lets the engine infer from definition.340- **Env vars (`WS_ID`, `DF_ID`, `API`, `RESOURCE`)** for script reuse.341- **Batch connection validation** — loop over `queryMetadata.json connections[]` and `GET /v1/connections/{id}` in one pass before refresh; optionally `POST /v1/connections/{id}/testConnection` to catch rotated credentials.342- **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.343344### TROUBLESHOOTING345346| Symptom | Fix |347|---|---|348| 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). |349| 405 Method Not Allowed on `getDefinition` | Use POST, not GET. |350| `updateDefinition` silently drops queries | Send all 3 parts (`mashup.pq`, `queryMetadata.json`, `.platform`). |351| `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). |352| `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`. |353| `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[]`. |354| 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](references/connection-management.md#resolving-clusterid-power-bi-v2). |355| 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. |356| Refresh fails with "Connection not found" | Extract `connectionId` (composite) from `queryMetadata.json`, parse `DatasourceId`, confirm via `GET /v1/connections/{id}`. |357| `connections[]` missing after `updateDefinition` | Read-modify-write rebuilt `queryMetadata.json` from a snapshot without bindings. Re-bind and `updateDefinition` again before refresh. |358| 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":"…"}`. |359| `formatVersion` mismatch error | Set `formatVersion: "202502"` in `queryMetadata.json`. |360| Fast copy not engaged | Add `[StagingDefinition = [Kind = "FastCopy"]]` before `section` in `mashup.pq`. |361| LRO polling returns 404 | Use the `Location` header URL — don't construct operation URLs manually. |362| 429 Too Many Requests | Respect `Retry-After`; exponential backoff. |363| Base64 decode produces garbage | Strip trailing newlines; use `base64 -w0` (Linux). |364| 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](references/authoring-script-templates.md#powershell--create-dataflow-with-definition). |365| 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. |366367---368369## Examples370371> **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](references/authoring-script-templates.md). On PowerShell, prefer `--body "@$env:TEMP\body.json"` and write the body via `[IO.File]::WriteAllText($path, $body, [System.Text.UTF8Encoding]::new($false))` over `Out-File` (which writes a UTF-8 BOM on Windows PowerShell 5.1 and breaks `az.exe` body parsing) and over inline `--body "{...}"` (which `cmd.exe` mangles).372373### Example 1: Create a Dataflow Gen2 from Scratch374375**Prompt**: "Create a new Dataflow Gen2 that reads a public CSV via the Web connector, and verify it."376377**Agent response** — runnable bash implementation of [Workflow A](#a-create-a-new-dataflow-end-to-end). PowerShell variant: [authoring-script-templates.md § End-to-End Smoke Test](references/authoring-script-templates.md#end-to-end-smoke-test).378379```bash380# Prereqs: az login, jq, base64, uuidgen. Workspace must support Dataflow Gen2.381WS_ID="<workspaceId>"382DF_NAME="my-titanic-df"383CONN_NAME="my-titanic-web-conn"384URL="https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv"385RES="https://api.fabric.microsoft.com"; API="$RES/v1"386PBI="https://analysis.windows.net/powerbi/api" # NO trailing slash387388# Step 1: List existing connections, try to reuse by name.389CONN_ID=$(az rest --method get --resource "$RES" --url "$API/connections" \390 --query "value[?displayName=='$CONN_NAME'] | [0].id" -o tsv)391392# Step 2: Create if missing (Web + Anonymous; see connection-management.md for other shapes).393if [ -z "$CONN_ID" ] || [ "$CONN_ID" = "null" ]; then394 BODY_FILE=$(mktemp --suffix=.json 2>/dev/null || mktemp) # GNU + BSD/macOS compatible395 cat > "$BODY_FILE" <<EOF396{397 "displayName": "$CONN_NAME",398 "connectivityType": "ShareableCloud",399 "connectionDetails"400401…(truncated)