Multi-tenant Copilot Studio deployment tool for MSPs. Short name: Pax8 CTA.
Pax8 CTA runs on Node.js, including the no-install npx path shown below. Check your version first:
node --versionIf it prints v20.12 or newer (CTA's declared engines floor), you're set — continue to the next section. If it prints an older version, or you see command not found, pick one of the install paths below.
- Any OS, no extra tooling required (recommended default): download the Node.js LTS installer from https://nodejs.org and run it. Works the same way on macOS, Windows, and Linux.
- Windows (built-in package manager):
winget install OpenJS.NodeJS.LTS— winget ships with Windows 10 and 11, no setup needed. - macOS or Linux with Homebrew:
brew install node— installs current Node, which is fine because it's past the v20.12 floor. (Homebrew is not native on Windows; use the installer or winget instead.) - No Node available, or a locked-down endpoint where you can't install runtimes: skip Node entirely and use the prebuilt Standalone Binaries under Installation — they're self-contained and need nothing else on the machine.
After any install path above, close the terminal, open a new one, and re-run node --version to confirm it now reports v20.12 or newer. The reopen step matters for both the installer and winget — they update PATH for new shells, not for shells that were already open. If something goes wrong, see the Troubleshooting section below.
DEMO_MODE=true npx -y -p @pax8/cta pax8-cta tenants listThat flips on demo mode (mock data, no Azure/GDAP needed) and lists the sample tenant fleet. From there, npx -y -p @pax8/cta pax8-cta --help shows every command.
Pax8 CTA exports Power Platform solutions (Copilot Studio agents) from a source Dataverse environment and imports them into customer tenants via GDAP (Granular Delegated Admin Privileges). It handles authentication, connection reference mapping, environment variables, staged rollouts, and rollback -- so you can deploy one agent to hundreds of customer environments in minutes.
npm install -g @pax8/cta
pax8-cta init # Set up credentials and config
pax8-cta validate # Verify GDAP access to all tenants
pax8-cta export --solution "CustomerServiceAgent"
pax8-cta deploy --solution ./exports/CustomerServiceAgent_managed.zip --allTime to first deployment: ~15 minutes (including GDAP setup). Time per subsequent deployment: ~2 minutes, whether it's 1 tenant or 100.
What is GDAP? Microsoft's secure delegation model that lets you access customer tenants without storing their credentials.
Setup in Partner Center:
- Navigate to Customers > Select your customer
- Go to Account > Admin relationships
- Click "Request a delegated admin relationship"
- Configure:
- Relationship name:
Pax8 CTA Deployment Access - Duration: 2 years (default)
- Roles to request: Power Platform Administrator (required)
- Relationship name:
- Send invitation link to customer
- Customer approves in their Microsoft 365 Admin Center
- Status changes to "Active"
Once active, Pax8 CTA automatically discovers this customer. No manual tenant configuration needed.
In your partner tenant's Azure Portal:
- Navigate to Azure Active Directory > App registrations > New registration
- Configure:
- Name:
Pax8 CTA Deployment Tool - Supported account types: Accounts in any organizational directory (Multitenant)
- Name:
- After creation, note these values:
- Application (client) ID
- Directory (tenant) ID (your partner tenant ID)
- Create client secret:
- Certificates & secrets > New client secret
- Copy the value immediately (shown only once)
- Add API permissions:
- API permissions > Add a permission
- Dynamics CRM > Delegated permissions >
user_impersonation - Click "Grant admin consent"
- Optional -- for GDAP discovery:
- Add Microsoft Graph > Delegated >
DelegatedAdminRelationship.Read.All - Add PowerApps Service > Delegated >
User
- Add Microsoft Graph > Delegated >
Only needed if you encounter permission issues. Most MSPs with GDAP + Power Platform Admin role don't need this.
If required, in each customer's environment:
- Power Platform Admin Center > Environments > [Environment] > Settings
- Users + permissions > Application users > New app user
- Add your partner app registration
- Assign: System Administrator or Solution Import role
npm install -g @pax8/ctaPre-built binaries are available for macOS (arm64, x64), Linux (arm64, x64), and Windows (x64). Download from the Releases page, or use the install scripts:
macOS / Linux:
curl -fsSL https://raw.githubusercontent.com/pax8labs/pax8-cta/main/install.sh | bashWindows (PowerShell):
irm https://raw.githubusercontent.com/pax8labs/pax8-cta/main/install.ps1 | iexIntegrity and OS warnings. Each release publishes a .sha256 file next to every binary, and the install scripts above verify the SHA256 checksum before installing. The binaries are not yet code-signed or notarized, so on first run you may see a Gatekeeper warning on macOS ("cannot be opened because the developer cannot be verified") or a SmartScreen warning on Windows ("Windows protected your PC"). On macOS you can clear the quarantine attribute with xattr -d com.apple.quarantine /usr/local/bin/pax8-cta; on Windows, click "More info" then "Run anyway." Signed and notarized binaries are tracked separately and will land in a future release.
git clone https://github.com/pax8labs/pax8-cta.git
cd pax8-cta
npm install -g pnpm
pnpm install && pnpm build
pnpm cli # Run the CLIRun pax8-cta with no arguments to enter interactive REPL mode, or use commands directly:
| Command | Description |
|---|---|
pax8-cta deploy |
Deploy a solution to multiple tenants |
pax8-cta export |
Export a solution from the source environment |
pax8-cta import |
Import a solution into a single tenant |
pax8-cta validate |
Verify GDAP access and connectivity for all tenants |
pax8-cta analyze |
Analyze a solution package before deployment |
pax8-cta tenants |
List, inspect, and manage customer tenants |
pax8-cta solutions |
List solutions in the source environment |
pax8-cta agents |
List agents and analyze drift across tenants |
pax8-cta deployments |
View deployment history and status |
pax8-cta auth |
Test authentication and token acquisition |
pax8-cta setup |
Configure credentials interactively |
pax8-cta init |
Initialize a new Pax8 CTA project with config files |
pax8-cta status |
Show current configuration and connection status |
pax8-cta telemetry |
View or change anonymous usage telemetry settings |
Deploy a solution to multiple tenants at once.
# Deploy to all enabled tenants
pax8-cta deploy --solution ./exports/CustomerServiceAgent_managed.zip --all
# Deploy to tenants with a specific tag
pax8-cta deploy --solution ./exports/CustomerServiceAgent_managed.zip --tag enterprise
# Deploy to multiple tag groups
pax8-cta deploy --solution ./exports/CustomerServiceAgent_managed.zip --tag enterprise --tag pilot
# Dry run (see what would be deployed without deploying)
pax8-cta deploy --solution ./exports/CustomerServiceAgent_managed.zip --all --dry-runExport a solution from your source environment.
# Export as managed (default)
pax8-cta export --solution "CustomerServiceAgent"
# Export as unmanaged
pax8-cta export --solution "CustomerServiceAgent" --unmanagedImport a solution into a single tenant (useful for testing).
pax8-cta import --solution ./exports/CustomerServiceAgent_managed.zip --tenant <tenant-id>Check GDAP access and environment connectivity for all configured tenants.
pax8-cta validateInspect a solution package to see components, dependencies, and connection references before deploying.
pax8-cta analyze --solution ./exports/CustomerServiceAgent_managed.zipManage and inspect your customer tenant fleet.
# List all tenants
pax8-cta tenants list
# Filter by tag
pax8-cta tenants list --tag enterprise
# Inspect a specific tenant
pax8-cta tenants show <tenant-id>
# Run health check
pax8-cta tenants health <tenant-id>List solutions available in the source environment.
pax8-cta solutions listView deployed agents and analyze drift across tenants.
# List agents
pax8-cta agents list
# Show agent details
pax8-cta agents show <agent-id>
# Analyze agent drift across tenants
pax8-cta agents driftMost commands support --format json for scripting and automation:
pax8-cta tenants list --format json
pax8-cta deployments list --format jsonversion: "2.0"
partner:
tenantId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
clientId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
source:
tenantId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
environmentUrl: "https://your-dev-org.crm.dynamics.com"
tenants:
- name: "Contoso Corporation"
tenantId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
environmentUrl: "https://contoso.crm.dynamics.com"
tags: ["enterprise", "wave1"]
enabled: trueRun pax8-cta init to generate this file interactively.
Map source connection references to target connections for each tenant:
tenants:
- name: "Contoso Corporation"
tenantId: "..."
environmentUrl: "..."
connectionMappings:
- sourceLogicalName: "cr_sharepoint_connection"
targetConnectionId: "shared-sharepoint-contoso-xxx"
- sourceLogicalName: "cr_outlook_connection"
targetConnectionId: "shared-office365-contoso-xxx"Configure tenant-specific environment variable values:
tenants:
- name: "Contoso Corporation"
environmentVariables:
- schemaName: "cr_SupportEmail"
value: "support@contoso.com"
type: "String"
- schemaName: "cr_MaxRetries"
value: 5
type: "Number"Deploy in stages with health checks between waves:
settings:
waves:
- name: "Pilot"
order: 1
tenants: ["wave1", "priority"]
maxParallel: 2
waitAfterCompletion: "5m"
continueOnFailure: false
- name: "Main Rollout"
order: 2
tenants: ["wave2"]
maxParallel: 10
continueOnFailure: trueEnable automatic snapshots and rollback:
settings:
rollback:
enabled: true
keepVersions: 3
autoRollbackOnFailure: false
rollbackTimeout: "10m"Send notifications to external systems on deployment events:
settings:
webhooks:
- url: "https://hooks.slack.com/services/xxx"
events:
- deployment.started
- deployment.completed
- deployment.failed
- tenant.failed
secret: "${WEBHOOK_SECRET}"
retries: 3| Variable | Description | Default |
|---|---|---|
PARTNER_CLIENT_SECRET |
Azure AD app client secret | Required |
CONFIG_PATH |
Path to tenants.yaml | ./config/tenants.yaml |
DEMO_MODE |
Enable demo mode with mock data. Per-process override that takes precedence over the persistent demo on/demo off setting |
false |
SNAPSHOTS_DIR |
Directory for rollback snapshots | ./snapshots |
Set credentials via pax8-cta setup or export them directly:
export PARTNER_CLIENT_SECRET="your-secret"- No credential storage -- Pax8 CTA uses GDAP delegation and never stores customer credentials
- Token lifecycle -- Access tokens expire after 1 hour and are automatically refreshed
- Least privilege -- Only requests minimum required API permissions
- Audit logging -- All operations are logged with timestamps and outcomes
- TLS required -- All API communication is over HTTPS
Pax8 CTA has comprehensive test coverage:
- Core library: 511 tests covering auth, config, Dataverse client, and services
- CLI: ~180 integration tests covering all commands, error handling, and edge cases
# Run all tests
pnpm test
# CLI tests only
pnpm --filter @pax8/cta test
# CLI tests with coverage
pnpm --filter @pax8/cta test:coverage
# Single test file
pnpm --filter @pax8/cta test -- --run src/__tests__/deploy.test.ts
# Core tests only
pnpm --filter @pax8/cta-core testTests run in demo mode by default (no Azure AD credentials or customer tenants required).
pax8-cta
├── packages/
│ ├── cli/ # CLI application (Commander.js)
│ │ └── src/
│ │ ├── commands/ # All CLI commands
│ │ └── index.ts # Entry point + REPL mode
│ │
│ └── core/ # Shared business logic
│ └── src/
│ ├── auth/ # GDAP + Azure AD token management
│ ├── config/ # YAML config schema (Zod validation)
│ ├── dataverse/ # Solution export/import, connection refs
│ └── services/ # Deployment, health, rollback, webhooks, waves
│
├── config/
│ └── tenants.yaml # Fleet configuration
└── exports/ # Exported solution files
┌──────────────┐
│ pax8-cta │
│ CLI │
└──────┬───────┘
│
┌──────▼───────┐
│ @pax8/ │
│ cta-core │
└──────┬───────┘
│
┌────────────┼────────────┐
│ │ │
┌──────▼──┐ ┌──────▼──┐ ┌──────▼──┐
│ Entra ID│ │Dataverse│ │ Graph │
│ (Auth) │ │ (API) │ │(Optional)│
└─────────┘ └────┬────┘ └──────────┘
│
┌──────────────┼──────────────┐
│ │ │
┌──────▼──────┐┌─────▼───────┐┌─────▼───────┐
│ Customer A ││ Customer B ││ Customer C │
│ Dataverse ││ Dataverse ││ Dataverse │
└─────────────┘└─────────────┘└─────────────┘
How it works: Copilot Studio agents are stored in Microsoft Dataverse (the database behind Power Platform). Pax8 CTA uses the Dataverse API to export your agent as a "solution" from your development environment, then imports it into each customer's environment using GDAP for cross-tenant authentication.
- Purpose: Solution import/export, agent management
- Key endpoints:
ExportSolution,ImportSolution,solutions,bots,connectionreferences,environmentvariabledefinitions - Auth: OAuth 2.0 with delegated permissions via GDAP
- Rate limits: 6,000 requests per 5 minutes per user (handled with exponential backoff)
- Docs: Dataverse Web API Reference
- Purpose: Authentication, cross-tenant token acquisition
- Permissions required:
Dynamics CRM > user_impersonation(delegated) - Grant type:
client_credentialswith GDAP delegation - Docs: Microsoft identity platform
- Purpose: GDAP relationship discovery, customer tenant enumeration
- Docs: Graph API Reference
- Purpose: Environment health checks, tenant validation
- Docs: Power Platform API
sequenceDiagram
participant CLI as Pax8 CTA CLI
participant Entra as Entra ID
participant GDAP as Partner Center
participant Tenant as Customer Tenant
CLI->>Entra: Request token (client credentials)
Entra->>GDAP: Verify GDAP relationship
GDAP-->>Entra: GDAP approved
Entra-->>CLI: Access token (customer tenant scope)
CLI->>Tenant: Call Dataverse API
Tenant-->>CLI: Solution deployed
| API | Limit | Handling |
|---|---|---|
| Dataverse Web API | 6,000 requests/5min per user | Exponential backoff, request queuing |
| Graph API | 12,000 requests/10sec | Batching, rate limit detection |
| Entra ID Token | 2,000 requests/min | Token caching (1hr TTL) |
- The terminal was open before the Node installer finished, so it still has the old
PATH. Close that terminal and open a new one (or runexec $SHELL), then try the command again. - If a fresh shell still fails, run
node --versionin that new shell. Ifnode --versionreports v20.12 or newer but the command you actually wanted (such asnpxor a globally installed binary) is still missing, that's a realPATHissue — check where your Node install put itsbindirectory and make sure it's onPATH. - If
node --versionalso fails in a brand-new shell, the Node install didn't actually complete. Reinstall from https://nodejs.org and let the installer finish before opening a new terminal.
- Don't reach for
sudo. It papers over the install once but leaves root-owned files in the global npm tree that npm itself can't manage later, which creates worse problems on the next upgrade. - The simplest fix is to skip the global install entirely and use the no-install
npxpath documented near the top of this README. - If you do want a global install, reinstall Node via a version manager (such as
nvmorfnm) so the global prefix lives inside your home directory. Global installs then no longer need root.
- Verify
PARTNER_CLIENT_SECRETis set correctly - Check that the app registration has admin consent
- Ensure the client secret hasn't expired
- Verify GDAP relationship is approved in Partner Center
- Check that the relationship includes Power Platform Administrator role
- Run
pax8-cta validateto check all tenants
- Ensure all solution dependencies are installed in the target environment
- Check that the solution was exported with "Add required objects"
- Pax8 CTA automatically retries with exponential backoff
- For large fleets, use deployment waves to stagger the rollout
- Verify the connection reference logical name matches the source
- Ensure the target connection ID exists in the customer environment
- Check that connections are shared with the application user
- make.powerapps.com does not support GDAP -- All operations must go through APIs
- Flows with owner-only triggers -- May require manual configuration in the target environment
- Cross-region deployments -- May experience higher latency (API calls traverse regions)
Pax8 CTA can collect anonymous usage telemetry to help improve the tool. Telemetry is disabled by default and must be explicitly opted into.
What's collected (when enabled): command names, flags used (names only, not values), success/failure, execution duration, CLI version, OS platform.
What's never collected: tenant IDs, solution names, file paths, environment URLs, error messages, or any personally identifiable information.
Opt in:
pax8-cta telemetry onOpt out:
pax8-cta telemetry off
# or
export PAX8_CTA_TELEMETRY_DISABLED=1Telemetry is also automatically disabled in CI environments (CI=true) and respects the DO_NOT_TRACK standard.
Apache 2.0 -- see LICENSE for details.
We currently use a publish-only open source model and do not accept external pull requests yet. Please see CONTRIBUTING.md for the latest policy and feedback channels.