6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Claude Code で Power Automate のフローを「API経由」で作る 〜GUIを使わず clientdata を直接いじる〜

6
Posted at

結論

  • 「Power Automate は GUI(デザイナー)でしか作れない」と思い込んでいましたが、Dataverse Web API で workflow テーブルの clientdata(フロー定義のJSON)を GET/PATCH すれば、コードだけでフローを作ったり直したりできます。
  • 認証は Azure CLI でトークンを取ります。このとき audience を Dataverse の環境URLと完全に一致させます。
  • 開発には Claude Code(PowerShell上で起動)と Python(標準ライブラリだけ)を使いました。

まずは完成形です。以下は、コード(API)だけで作ったフローが Power Automate ポータルの一覧に並んでいる画面です。
image.png

対象読者・ゴール

  • 対象読者:Power Automate を GUI で触ったことがあり、AIコーディングツールでフローを作成したい人
  • ゴール:トークン取得 → 既存フローの clientdata 取得 → 編集 → PATCH で反映、まで一周できること

※Power Platform のAPIは環境・テナント設定に依存します。本記事は
【2026年6月時点/Dataverseの開発環境】で動作確認した内容です。

なぜ GUI ではなく API なのか

きっかけは「Claude Code(AIコーディング支援)で Power Automate を作れないか」という思いつきでした。
当初は「フローはGUI専用、コードからは無理」と思っていたのですが、調べてみると次のことが分かりました。

  • Power Automate のクラウドフローは、Dataverse の workflow テーブルの1レコードにすぎません
  • フロー定義の実体は、その clientdata カラムに入った JSON 文字列です

このカラムを Dataverse Web API で読み書きすれば、GUIなしでフローを操作できます。

以下は、フローの正体が Dataverse の1レコード(workflow テーブルの1行)であることを示す画面です。
image.png

前提条件

項目 内容
Dataverse 環境 dev
権限 System Customizer 以上
認証 Azure CLI でユーザートークン取得
実行環境 Windows / PowerShell + Python 3.8+(標準ライブラリ urllib のみ、pip不要)
AIアシスタント Claude Code

全体像

以下は、これから行う処理の全体像(トークン取得 → GET → 編集 → PATCH の流れ)です。
全体像_PowerAutomateWebAPI.png

手順

Step 1. トークン取得

Azure CLI で、Dataverse 環境URLを audience に指定してトークンを取得します。
具体的には、az account get-access-token--resource に環境URLを渡します。
ここで指定した値が、そのままトークンの aud(宛先)になります。

az login
az account get-access-token `
  --resource https://yourorg.crm7.dynamics.com `
  --query accessToken -o tsv > dataverse_token.txt

yourorg.crm7.dynamics.com はダミー表記です(実際の値を入力してください)。

1つ目のつまずきポイントがここでした。
--resource は環境URLとぴったり一致していないと通りません。(当たり前ではありますが💦)
https://disco.crm.dynamics.com のような汎用URLだと、後の PATCH が 401/403 で落ちます。
トークンの aud クレームをデコードして環境URLと一致するか確認する小さな検証コードを用意しておくと、この手のつまずきを防げます。

Step 2. 操作対象フローの workflowId を取得

操作対象フローの workflowId を取得します。
なお、$select$filter を付けたURLは、$ や空白・シングルクォートといった記号を含みます。これらをそのまま送るとサーバー側で解釈できず 400(Bad Request)になるため、クエリ部分だけを安全にURLエンコードしてから送ります。

import json, urllib.request, urllib.parse

BASE = "https://yourorg.crm7.dynamics.com/api/data/v9.2"  # ← ダミー表記(実際の値を入力してください)
TOKEN = open("dataverse_token.txt").read().strip()
HEAD = {"Authorization": f"Bearer {TOKEN}", "Accept": "application/json"}

# $filter の値(フロー名)もダミー表記(実際の値を入力してください)
query = "$select=name,workflowid,statecode&$filter=name eq 'MyFlow'"
# $ や空白などを安全にエンコード(= & $ , ( ) ' は残す)
safe_q = urllib.parse.quote(query, safe="=&$,()'")
url = f"{BASE}/workflows?{safe_q}"

req = urllib.request.Request(url, headers=HEAD)
flows = json.load(urllib.request.urlopen(req))["value"]
print(flows[0]["workflowid"])

ここでの2つ目のつまずきが404でした。
GUIはURLにすぐGUIDを表示しますが、「保存」するまで Dataverse に実体(workflowレコード)は作られません。
そのため受け取ったGUIDがことごとく404になり、原因が分からず時間を食いました。
API操作の前に、GUIで一度保存しておく必要があります。

※以下は、空フローに名前と最低限のトリガー・アクションだけ付けて「保存」→「公開」する画面です。今回は「demo」フローを作成しました。
image.png
image.png

Step 3. 現在の clientdata を取得

次に、PATCHの前に必ずGETしてバックアップを取ります。
編集前のものを flows/ に残しておけば、おかしくなってもすぐ元に戻せます。

WID = "<あなたのworkflowId>"  # ← Step 2 で取得した値を入力してください
url = f"{BASE}/workflows({WID})?$select=name,clientdata"
cur = json.load(urllib.request.urlopen(urllib.request.Request(url, headers=HEAD)))
open("clientdata_backup.json", "w", encoding="utf-8").write(cur["clientdata"])

Step 4. clientdata を編集

ここが一番ハマりやすいところです。
前提、clientdata には JSON オブジェクトそのものではなく、JSONを文字列化したものが入っています。
中身は Azure Logic Apps の Workflow Definition スキーマ(triggers / actions / outputs)で、Power Automate のクラウドフローは内部的に Logic Apps と同じ定義言語を採用しているため、@{} の式表現などもそのまま使えます。

編集の基本は「取得した文字列を json.loads でオブジェクトに戻す → 必要なアクションを足し引きする → clientdata_new.json に保存する」という流れです。ここで保存したファイルを、次の Step 5 で書き戻します。

例として、「Dataverse のレコードを1件更新するアクション」を1つ組み込んでみます。
ポイントは、entityNameoperationId でアクションの種類が決まること、item に更新したいフィールドをまとめて渡せること、@{} の式で他アクションやトリガーの出力を参照できることです(列名・値はダミー表記です)。

# ① Step 3 で取得した cur["clientdata"] は「文字列」。loads でオブジェクトに戻す
definition = json.loads(cur["clientdata"])

# ② 目的のアクションを組み込む(ここでは「レコードを更新」を1つ追加)
definition["properties"]["definition"]["actions"]["レコードを更新"] = {
    "runAfter": {},
    "type": "OpenApiConnection",
    "inputs": {
        "parameters": {
            "organization": "current",
            "entityName": "your_entities",                        # 更新先テーブル(ダミー表記)
            "recordId": "@triggerOutputs()?['body/your_recordid']",
            "item": {                                             # 更新する列をまとめて渡す
                "your_status": 100000002,
                "your_note": "@triggerBody()?['text']"
            }
        },
        "host": {
            "apiId": "/providers/Microsoft.PowerApps/apis/shared_commondataserviceforapps",
            "connectionName": "shared_commondataserviceforapps",
            "operationId": "UpdateRecordWithOrganization"         # アクションの種類
        },
        "authentication": "@parameters('$authentication')"
    }
}

# ③ 編集後のオブジェクトを保存(Step 5 で読み込みます)
json.dump(definition, open("clientdata_new.json", "w", encoding="utf-8"),
          ensure_ascii=False, indent=2)

item は「フィールド名: 値」のオブジェクトを丸ごと受け取れるので、更新したい列が増えても、この中にキーを足すだけで済みます(UIで組むとフィールドごとに1行ずつ増えていきます)。

上のコードで行っているのは「文字列 → オブジェクト(json.loads)」までで、逆の「オブジェクト → 文字列(json.dumps)」は Step 5 の書き戻し時に行います。

なお、アクションをゼロから手書きするのは大変です。
特に Teams のような動的スキーマ(投稿先や投稿者の選び方で、必要なパラメータの構造そのものが変わるアクション)は、GUIで一度だけ構成して保存し、生成された clientdata をコピーして使うのが確実です(推測で組むとGUI側に警告が出ます)。

Step 5. PATCH で書き戻す

step 4で言及した書き戻しを行います。

new = json.load(open("clientdata_new.json", encoding="utf-8"))
body = json.dumps({"clientdata": json.dumps(new, ensure_ascii=False)},
                  ensure_ascii=False).encode("utf-8")
req = urllib.request.Request(f"{BASE}/workflows({WID})", data=body, method="PATCH",
        headers={**HEAD, "Content-Type": "application/json", "If-Match": "*"})
print(urllib.request.urlopen(req).status)  # 204 = 成功

PATCH が 204 を返せば成功です。Power Automate ポータルで反映を確認します。
反映されていたら、無事完了です。

最後に

初投稿かつAIにサポートしてもらって投稿しました。
誤りや「うちの環境では違った」といった点があれば、コメントで教えてください!
読んでいただきありがとうございました!

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?