apiops-cli generates ready-to-use GitHub Actions workflows for extracting and publishing APIM configuration. This guide walks through setup, configuration, and customization.
- An Azure API Management instance (dev and optionally prod)
- A GitHub repository for your APIM configuration
- An App Registration in Microsoft Entra ID with federated credentials for your repo
- Node.js 22.x (used in workflows)
The fastest way to get started is with apiops init:
apiops init --ci github-actionsThis generates:
.github/
└── workflows/
├── run-extractor.yaml # Manual extract workflow
└── run-publisher.yaml # Publish on push to main
If you also want Azure DevOps pipelines, omit the
--ciflag and select interactively, or use--ci azure-devops.
File: .github/workflows/run-extractor.yaml
Trigger: Manual (workflow_dispatch)
The extract workflow pulls configuration from your APIM instance and creates a PR with the changes.
| Input | Description | Options |
|---|---|---|
ENVIRONMENT |
Which APIM instance to extract from | dev, prod |
CONFIGURATION_YAML_PATH |
Extract all APIs or use a filter file | Extract All APIs, configuration.extractor.yaml |
- Validates secrets — checks that required secrets are configured for the selected environment.
- Checks out the repository and installs Node.js 22 + npm dependencies.
- Authenticates with Azure using
azure/login@v2with OIDC federated credentials. - Runs
apiops extract— either extracting all APIs or using a filter configuration file. - Uploads artifacts — stores extracted files as a GitHub Actions artifact (30-day retention).
- Creates a pull request — opens a PR with the extracted changes on a new branch (
apim-extract-<run-id>).
permissions:
id-token: write # OIDC token for Azure login
contents: write # Push extracted files
pull-requests: write # Create PR with changes- Go to Actions → Run APIM Extractor → Run workflow.
- Select the environment (
devorprod). - Choose whether to extract all APIs or use a filter file.
- Click Run workflow.
- When complete, review and merge the auto-created PR.
File: .github/workflows/run-publisher.yaml
Trigger: Push to main (when artifact or config files change) + manual workflow_dispatch
The publish workflow deploys APIM configuration to your target environment.
The workflow runs automatically when changes are pushed to main in these paths:
<artifact-dir>/**— any change to extracted artifactsconfiguration.*.yaml— any change to configuration files
| Input | Description | Options |
|---|---|---|
COMMIT_ID_CHOICE |
Incremental (last commit) or full publish | publish-artifacts-in-last-commit, publish-all-artifacts-in-repo |
ENVIRONMENT |
Which APIM instance to publish to | Per your configured environments (e.g., dev, prod) |
- Resolves the commit ID — captures
GITHUB_SHAfor incremental publish. - Checks out the repository with
fetch-depth: 2(needed for git diff). - Authenticates with Azure using OIDC federated credentials.
- Substitutes tokens — replaces
{#[TOKEN_NAME]#}placeholders inconfiguration.<env>.yamlwith pipeline secret values. - Runs a dry-run validation — executes
apiops publish --dry-runto verify the publish would succeed. If this fails, the workflow halts and the real publish is never attempted. This prevents partial failures from leaving APIM in an inconsistent state. - Runs
apiops publishin one of two modes:- Incremental (default): uses
--commit-idto publish only changed files. - Full: publishes all artifacts in the repository (useful for recovery or initial setup).
- Incremental (default): uses
permissions:
id-token: write # OIDC token for Azure login
contents: read # Read artifact files| Mode | When to use | Command |
|---|---|---|
| Incremental (default) | Normal deployments — only deploys what changed in the last commit | apiops publish --commit-id <sha> |
| Full | Recovery after failed publish, initial setup, or force-sync | apiops publish (no --commit-id) |
Tip: If a publish fails partway through, re-run the workflow manually with
publish-all-artifacts-in-repoto ensure full consistency.
Create a GitHub environment for each target APIM instance:
- Go to Settings → Environments → New environment.
- Create environments matching your workflow (e.g.,
dev,prod). - Optionally add protection rules (required reviewers, wait timer) for production.
Configure these secrets in each GitHub environment:
| Secret | Description | Example |
|---|---|---|
AZURE_CLIENT_ID |
App Registration client ID | aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee |
AZURE_TENANT_ID |
Microsoft Entra ID tenant ID | aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee |
AZURE_SUBSCRIPTION_ID |
Azure subscription ID | aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee |
APIM_RESOURCE_GROUP_DEV |
Resource group for dev APIM | rg-apim-dev |
APIM_SERVICE_NAME_DEV |
Dev APIM service name | apim-contoso-dev |
APIM_RESOURCE_GROUP_PROD |
Resource group for prod APIM | rg-apim-prod |
APIM_SERVICE_NAME_PROD |
Prod APIM service name | apim-contoso-prod |
Note:
APIM_RESOURCE_GROUP_*andAPIM_SERVICE_NAME_*secrets are suffixed with the environment name in uppercase (e.g.,_DEV,_PROD). The extract workflow selects the right secret based on theENVIRONMENTinput.
OIDC (OpenID Connect) lets GitHub Actions authenticate to Azure without storing secrets. Instead, GitHub's OIDC provider issues a short-lived token that Azure trusts.
-
Create an App Registration in Microsoft Entra ID:
az ad app create --display-name "apiops-github-actions" -
Create a service principal:
az ad sp create --id <app-id>
-
Add federated credentials for your GitHub repository:
# For the "dev" environment az ad app federated-credential create --id <app-id> --parameters '{ "name": "github-dev", "issuer": "https://token.actions.githubusercontent.com", "subject": "repo:<owner>/<repo>:environment:dev", "audiences": ["api://AzureADTokenExchange"] }' # For the "prod" environment az ad app federated-credential create --id <app-id> --parameters '{ "name": "github-prod", "issuer": "https://token.actions.githubusercontent.com", "subject": "repo:<owner>/<repo>:environment:prod", "audiences": ["api://AzureADTokenExchange"] }'
-
Assign RBAC roles on your APIM instances:
az role assignment create \ --assignee <app-id> \ --role "API Management Service Contributor" \ --scope /subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.ApiManagement/service/<apim-name>
-
Add secrets to your GitHub environments (see Required Secrets).
For more on authentication methods, see the Authentication Guide.
Use GitHub environment protection rules for production deployments:
- Go to Settings → Environments → prod.
- Check Required reviewers and add team members.
- Optionally add a Wait timer (e.g., 5 minutes).
The publish workflow will pause and wait for approval before deploying to prod.
To replace {#[TOKEN_NAME]#} placeholders in your configuration YAML with pipeline secrets, add the secret mappings to the env: block of the generated substitution step:
- name: Substitute tokens in configuration.prod.yaml
uses: cschleiden/replace-tokens@v1.3
with:
tokenPrefix: '{#['
tokenSuffix: ']#}'
files: '["configuration.prod.yaml"]'
env:
MY_SECRET: ${{ secrets.MY_SECRET }}
BACKEND_URL: ${{ secrets.BACKEND_URL }}See the Token Substitution Guide for full details, including migration from APIOps Toolkit.
To use environment-specific overrides, add the --overrides flag to the publish step in the workflow:
- name: Publish to prod
run: |
npx apiops publish \
--subscription-id ${{ secrets.AZURE_SUBSCRIPTION_ID }} \
--resource-group ${{ secrets.APIM_RESOURCE_GROUP_PROD }} \
--service-name ${{ secrets.APIM_SERVICE_NAME_PROD }} \
--source apim-artifacts \
--overrides overrides.prod.yamlTo deploy to staging first, then prod (with approval), modify the publish workflow to add dependencies between environment jobs:
publish-prod:
needs: [get-commit, publish-staging]
environment: prod
# ... same steps as other env jobsAdd a dry-run step before the actual publish to preview changes:
- name: Dry run
run: |
npx apiops publish --dry-run \
--subscription-id ${{ secrets.AZURE_SUBSCRIPTION_ID }} \
--resource-group ${{ secrets.APIM_RESOURCE_GROUP_PROD }} \
--service-name ${{ secrets.APIM_SERVICE_NAME_PROD }} \
--source apim-artifacts| Problem | Cause | Fix |
|---|---|---|
AADSTS70025 or AADSTS700213 |
Federated credential subject doesn't match | Verify the subject uses repo:<owner>/<repo>:environment:<env> format |
AuthorizationFailed |
Missing RBAC role | Assign API Management Service Contributor to the App Registration |
APIM_RESOURCE_GROUP secret is not set |
Secret not configured for the environment | Add APIM_RESOURCE_GROUP_DEV / APIM_RESOURCE_GROUP_PROD to the correct GitHub environment |
| Extract succeeds but PR is empty | No changes detected | Artifacts match what's already in the repo — no new changes to commit |
| Publish runs on every push | Path filter too broad | Ensure the paths filter in the workflow matches only your artifact directory |
id-token: write permission error |
Workflow missing OIDC permission | Add permissions: id-token: write to the workflow or job |
For authentication issues, see the Authentication Guide.
- Authentication Guide — all auth methods and RBAC roles
- Environment Overrides — per-environment configuration
- Token Substitution — pipeline placeholder substitution with
{#[TOKEN_NAME]#} - Scenarios and Workflows — portal-first vs. code-first patterns