Minimal JavaScript CLI for authenticating with Microsoft Graph and extending the same app registration/auth flow to Microsoft Power Automate cloud-flow management through the Dataverse Web API.
This project now supports two Microsoft API surfaces:
- Microsoft Graph delegated-user auth and requests
- Power Automate cloud-flow management through Dataverse Web API endpoints
- Microsoft Graph auth concepts
- Microsoft identity platform authorization code flow
- Register an application with the Microsoft identity platform
- How to add a redirect URI to your application
- Work with cloud flows using code
- Use OAuth authentication with Microsoft Dataverse
- View developer resources
- Create an app registration in Microsoft Entra ID.
- Under
Authentication, add theMobile and desktop applicationsplatform. - Add a localhost redirect URI such as:
http://localhost:8787/callback
- Copy the app's
Application (client) ID. - Add delegated Microsoft Graph permissions that match what you want to do.
- For Power Automate, identify your Dataverse environment URL from
Power Apps -> Settings -> Developer resources.
Example Dataverse environment URL:
https://contoso.crm.dynamics.com
Good starter Microsoft Graph delegated permissions:
User.ReadFiles.ReadMail.Read
The default Graph login scope is:
openid profile offline_access User.Read
The default Power Automate login scope is computed from your environment URL and follows the Dataverse docs:
openid profile offline_access https://contoso.crm.dynamics.com/user_impersonation
cd ~/Projects/graph-api-cli
chmod +x ./bin/graph-api.js
npm run check
npm linkThat exposes the graph-api command in your shell.
export GRAPH_CLIENT_ID=...
export GRAPH_CLIENT_SECRET=...
export GRAPH_TENANT=common
export GRAPH_REDIRECT_URI=http://localhost:8787/callback
export GRAPH_SCOPES="openid profile offline_access User.Read Files.Read Mail.Read"
graph-api auth loginYou can reuse the same Entra app/client ID. Power Automate auth is stored separately from Graph auth in the same config file.
export POWER_AUTOMATE_CLIENT_ID=...
export POWER_AUTOMATE_TENANT=common
export POWER_AUTOMATE_REDIRECT_URI=http://localhost:8787/callback
export POWER_AUTOMATE_ENVIRONMENT_URL=https://contoso.crm.dynamics.com
graph-api power-automate auth loginYou can also pass the environment URL directly:
graph-api power-automate auth login \
--client-id ... \
--environment-url https://contoso.crm.dynamics.comConfig is stored at:
~/.config/graph-api-cli/config.json
graph-api help
graph-api auth login --client-id <id>
graph-api auth login --client-id <id> --client-secret <secret>
graph-api auth status
graph-api auth refresh
graph-api auth logout
graph-api me
graph-api me drive
graph-api me messages --limit 10
graph-api users get --id user@contoso.com
graph-api request GET /me
graph-api request GET /me/events --query '$top=5'
graph-api request PATCH /me --data-json '{"city":"Chicago"}'
graph-api request PUT /me/drive/root:/OpenClaw/notes.md:/content --input-raw ./notes.mdgraph-api power-automate auth login --client-id <id> --environment-url https://contoso.crm.dynamics.com
graph-api power-automate auth status
graph-api power-automate auth refresh
graph-api power-automate auth logout
graph-api power-automate whoami
graph-api power-automate capability-report
graph-api power-automate plan "When I get an email from a VIP sender, post it to Teams"
graph-api power-automate questions --intent "Create a daily approval flow"
graph-api power-automate scaffold --intent-json '{"intent":"Create a daily approval flow","draftTemplate":"approval"}'
graph-api power-automate templates list
graph-api power-automate templates show --id scheduled-basic
graph-api power-automate templates instantiate --id scheduled-basic --params-json '{"name":"Daily digest"}'
graph-api power-automate connections list
graph-api power-automate connections get --id <connection-id>
graph-api power-automate connection-references list
graph-api power-automate connection-references get --id <reference-id>
graph-api power-automate connection-references bind --id <reference-id> --connection-id <connection-id>
graph-api power-automate validate --input ./flow-create.json
graph-api power-automate preflight --input ./flow-create.json --environment <environment-id-or-url>
graph-api power-automate environments list
graph-api power-automate environments get --id <environment-id>
graph-api power-automate environments resolve --url https://contoso.crm.dynamics.com
graph-api power-automate environments select --id <environment-id>
graph-api power-automate solutions list
graph-api power-automate solutions get --id <solution-id>
graph-api power-automate solutions flows list --solution-id <solution-id>
graph-api power-automate solutions add-flow --solution-id <solution-id> --flow-id <flow-id>
graph-api power-automate runs list --flow-id <workflow-id> --top 20
graph-api power-automate runs get --id <run-id>
graph-api power-automate flows list --top 10 --state on
graph-api power-automate flows list --environment <environment-id-or-url>
graph-api power-automate flows get --id <workflow-id>
graph-api power-automate flows doctor --id <workflow-id>
graph-api power-automate flows triggers list --id <workflow-id>
graph-api power-automate flows actions list --id <workflow-id>
graph-api power-automate flows dependencies --id <workflow-id>
graph-api power-automate flows export --id <workflow-id> --output ./flow.json
graph-api power-automate flows import --input ./flow.json
graph-api power-automate flows import --input ./flow.json --apply
graph-api power-automate flows diff --id <workflow-id> --input ./flow.json
graph-api power-automate flows create-scheduled --name "Daily digest" --schedule "0 9 * * *"
graph-api power-automate flows create-teams-alert --name "Ops alert" --channel <channel-id>
graph-api power-automate flows create-approval --name "Review request" --approver approver@contoso.com
graph-api power-automate flows create --input ./flow-create.json
graph-api power-automate flows update --id <workflow-id> --input ./flow-patch.json
graph-api power-automate flows delete --id <workflow-id>
graph-api power-automate flows on --id <workflow-id>
graph-api power-automate flows off --id <workflow-id>
graph-api power-automate request GET /workflows --query '$top=5'- The supported API surface here is Dataverse Web API flow management, matching Microsoft Learn's
Work with cloud flows using codeguidance. - Environment discovery uses the Power Platform API and can select a default environment for later flow commands.
whoamiuses the DataverseWhoAmIfunction in the active environment to confirm the current principal context.- Run history uses the Dataverse
flowrunstable, which Microsoft documents for solution-aware cloud flows. - Connections are discovered through the Power Platform connectivity API.
- Connection references are discovered and updated through the Dataverse
connectionreferencestable. validateperforms offline structural checks on a Dataverse workflow payload before create/update.preflightadds environment-aware checks for missing or unbound connection references when an environment is available.- Solutions are discovered through the Dataverse
solutiontable. solutions add-flowuses the DataverseAddSolutionComponentaction and only works with unmanaged solutions.flows exportwrites a reusable workflow payload for version control and later import.flows importis safe by default and only applies changes when--applyis passed.flows diffcompares normalized trigger/action/reference structure between an existing flow and a local payload.flows triggers list,flows actions list, andflows dependenciesexpose a flow's structure in a stable agent-friendly shape.templatesexposes reusable starter payloads for common flow patterns.flows create-scheduled,flows create-teams-alert, andflows create-approvalare high-level builders that generate draft payloads and only apply when--applyis passed.planconverts natural language into a deterministic trigger/action/template recommendation.questionsextracts the most important unanswered configuration questions from an intent.scaffoldturns structured intent JSON into a starter payload, and stays dry-run unless--applyis passed.- Connector-backed builders depend on the relevant connection references being available and bound in the target environment.
flows doctorinspects the flow'sclientdataconnection references and highlights missing or unbound references.- Flow commands now inherit the selected environment by default, and you can override it per command with
--environmentor--environment-url. - This targets solution-aware cloud flows stored in Dataverse.
- Microsoft explicitly notes that
api.flow.microsoft.comis unsupported for customers and subject to breaking change. - Managing
My Flowswith code is not supported by the Microsoft Learn article this CLI is based on. - Creating flows requires a valid Dataverse
workflowpayload, including fields likecategory,name,type,primaryentity, andclientdata. - The
clientdatafield is the string-encoded JSON flow definition plus connection references from the Dataverse docs.
Confirm Graph auth and inspect the signed-in user:
graph-api meList active Power Automate cloud flows:
graph-api power-automate flows list --state onList environments and select a default one:
graph-api power-automate environments list
graph-api power-automate environments select --id Default-123456Inspect the current Power Automate principal and capability surface:
graph-api power-automate whoami
graph-api power-automate capability-reportInspect available connections and connection references:
graph-api power-automate connections list
graph-api power-automate connection-references listInspect and instantiate starter templates:
graph-api power-automate templates list
graph-api power-automate templates show --id scheduled-basic
graph-api power-automate templates instantiate \
--id scheduled-basic \
--params-json '{"name":"Daily digest","schedule":"0 9 * * *"}'Plan and scaffold a flow from natural language intent:
graph-api power-automate plan \
"When I get an email from a VIP sender, summarize it, post it to Teams, and create a Planner task if urgent"
graph-api power-automate questions \
--intent "When I get an email from a VIP sender, summarize it, post it to Teams, and create a Planner task if urgent"
graph-api power-automate scaffold \
--intent-json '{"intent":"When I get an email from a VIP sender, summarize it, post it to Teams, and create a Planner task if urgent","draftTemplate":"teams-alert"}'Bind a connection reference to a concrete connection:
graph-api power-automate connection-references bind \
--id 00000000-0000-0000-0000-000000000000 \
--connection-id /providers/Microsoft.PowerApps/apis/shared_office365/connections/shared-office365-123Validate a flow payload before applying it:
graph-api power-automate validate --input ./flow-create.json
graph-api power-automate preflight --input ./flow-create.json --environment Default-123456Inspect solutions and list the flows inside a solution:
graph-api power-automate solutions list
graph-api power-automate solutions flows list --solution-id 00000000-0000-0000-0000-000000000000Attach a flow to an unmanaged solution:
graph-api power-automate solutions add-flow \
--solution-id 00000000-0000-0000-0000-000000000000 \
--flow-id 11111111-1111-1111-1111-111111111111Export, diff, and safely import a flow payload:
graph-api power-automate flows export \
--id 00000000-0000-0000-0000-000000000000 \
--output ./flow.json
graph-api power-automate flows diff \
--id 00000000-0000-0000-0000-000000000000 \
--input ./flow.json
graph-api power-automate flows import --input ./flow.json
graph-api power-automate flows import --input ./flow.json --applyGenerate high-level flow drafts from ergonomic flags:
graph-api power-automate flows create-scheduled \
--name "Daily digest" \
--schedule "0 9 * * *"
graph-api power-automate flows create-teams-alert \
--name "Ops alert" \
--channel <channel-id>
graph-api power-automate flows create-approval \
--name "Review request" \
--approver approver@contoso.comInspect a flow's triggers, actions, and connector dependencies:
graph-api power-automate flows triggers list \
--id 00000000-0000-0000-0000-000000000000
graph-api power-automate flows actions list \
--id 00000000-0000-0000-0000-000000000000
graph-api power-automate flows dependencies \
--id 00000000-0000-0000-0000-000000000000Inspect recent runs for a solution-aware cloud flow:
graph-api power-automate runs list --flow-id 00000000-0000-0000-0000-000000000000 --top 10Diagnose whether a flow has missing or unbound connection references:
graph-api power-automate flows doctor --id 00000000-0000-0000-0000-000000000000Fetch a specific cloud flow:
graph-api power-automate flows get --id 00000000-0000-0000-0000-000000000000Run a raw Dataverse Web API request against workflows:
graph-api power-automate request GET /workflows --query '$select=name,workflowid,statecode' --query '$top=5'Upload a markdown file into OneDrive:
graph-api request PUT /me/drive/root:/OpenClaw/notes.md:/content --input-raw ./notes.md- Preferred setup: register the redirect URI under
Mobile and desktop applicationsso Entra treats this as a public client and no client secret is needed. - If the redirect URI is registered under
Web, Entra treats the token exchange as confidential-client auth and you must provide a client secret. - The CLI supports both modes.
- Access tokens expire quickly; the CLI refreshes access tokens automatically when a refresh token is available.
- Refresh-token lifetime and tenant policies can still force you to re-run login.
- Some Graph permissions and Dataverse permissions require admin consent.
- Graph requests target
https://graph.microsoft.com/v1.0unless you pass a full URL. - Power Automate requests target
<environment-url>/api/data/v9.2unless you pass a full URL.