Mastering API Deployments with API Ops: Azure DevOps vs GitHub Implementation Guide

• API Management, API Ops, DevOps, Azure DevOps, GitHub Actions, CI/CD, Azure, Code Night, New Zealand • 24 min read

Welcome to another insightful session from Code Night New Zealand! In this special presentation, we dive deep into API Ops methodology and explore how to implement automated API deployments using both Azure DevOps and GitHub.

What is API Ops?

Understanding the Evolution: DevOps → GitOps → API Ops

API Ops is the natural evolution of DevOps practices specifically applied to the API development lifecycle. Let’s break down this progression:

DevOps Foundation

DevOps is a methodology that integrates and automates the work of software development and IT operations, binding together practices from different teams:

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   DEVELOP   │    │   VERIFY    │    │  PACKAGE    │
│  Planning   │───▶│   Testing   │───▶│  Release    │
│  Coding     │    │ Integration │    │   Deploy    │
└─────────────┘    └─────────────┘    └─────────────┘
       ▲                                      │
       │                ┌─────────────┐      │
       │                │  CONFIGURE  │      │
       └────────────────┤  Monitor    │◄─────┘
                        │ Operations  │
                        └─────────────┘

GitOps Evolution

GitOps introduces version control as the single source of truth, with Git being the most popular choice. This creates a systematic approach to infrastructure and application management.

API Ops Definition

API Ops applies DevOps and GitOps practices specifically to the API development lifecycle, encompassing:

  • API Design: OpenAPI specifications and contracts
  • API Development: Implementation and testing
  • API Deployment: Automated provisioning and configuration
  • API Management: Policies, monitoring, and governance
  • API Operations: Maintenance and continuous improvement

API Ops Applied to Azure API Management

The API Ops Workflow

┌─────────────────┐    ┌─────────────────────────────┐
│ API Developers  │    │ API Operators               │
│                 │    │                             │
│ • Design APIs   │    │ • Create Policies           │
│ • Create Specs  │    │ • Configure Backends        │
│ • Test APIs     │    │ • Setup Monitoring          │
└─────────┬───────┘    └─────────────┬───────────────┘
          │                          │
          └──────────┐    ┌──────────┘
                     ▼    ▼
              ┌─────────────────┐
              │ Git Repository  │
              │                 │
              │ • API Specs     │
              │ • Policies      │
              │ • Configuration │
              └─────────┬───────┘
                        │
                        ▼
              ┌─────────────────┐
              │ Automated PR    │
              │ Creation        │
              └─────────┬───────┘
                        │
                        ▼
              ┌─────────────────┐
              │ Code Review &   │
              │ Approval        │
              └─────────┬───────┘
                        │
                        ▼
              ┌─────────────────┐
              │ Merge to Main   │
              │ Branch          │
              └─────────┬───────┘
                        │
                        ▼
              ┌─────────────────┐
              │ Automated       │
              │ Deployment      │
              │ Pipeline        │
              └─────────┬───────┘
                        │
                        ▼
              ┌─────────────────┐
              │ API Management  │
              │ Instance        │
              └─────────┬───────┘
                        │
                        ▼
              ┌─────────────────┐
              │ Extract Current │◄──┐
              │ State & Compare │   │
              └─────────┬───────┘   │
                        │           │
                        ▼           │
                   ┌─────────┐      │
                   │Changes? │──────┘
                   │ Yes/No  │
                   └─────────┘

Key Participants and Responsibilities

API Developers

  • Design and create OpenAPI specifications
  • Define API contracts and schemas
  • Test API implementations
  • Submit changes via pull requests

API Operators

  • Create and manage API policies
  • Configure backends and routing
  • Set up monitoring and diagnostics
  • Manage API governance and compliance

The API Ops Lifecycle

Scenario 1: Extract-First Approach

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Step 1: Check Git Repository
- name: Check Repository Content
  condition: repository_empty
  action: extract_current_state

# Step 2: Extract from API Management
- name: Extract API Management Configuration
  pipeline: extractor
  output: pull_request

# Step 3: Developer Changes
- name: Create API Specifications
  actor: developers
  output: pull_request

# Step 4: Review and Approval
- name: Code Review Process
  reviewers: [api_operators, lead_developers]
  gates: [spectral_validation, policy_compliance]

# Step 5: Automated Deployment
- name: Publish Changes
  pipeline: publisher
  environments: [dev, test, prod]

Scenario 2: Configuration-First Approach

1
2
3
4
5
6
7
8
# Operators modify schemas and configurations
- name: Update API Policies
  actor: operators
  files: [policies/*.xml, backends/*.yml]

# Follow same PR process
- name: Standard Review Process
  includes: [validation, approval, merge, deploy]

Azure DevOps Implementation

Prerequisites and Setup

1. Download API Ops Toolkit

Visit the API Ops GitHub Repository and download the latest release:

1
2
3
4
5
6
7
# Extract the toolkit
unzip apiops-release.zip

# Available pipelines:
├── run-extractor.yml      # Extracts current API Management state
├── run-publisher.yml      # Publishes changes to API Management
└── run-publisher-with-env.yml  # Multi-environment publisher

2. Service Connection Configuration

Recommended Approach: Use Workload Identity Federation (no secrets required)

1
2
3
4
5
6
7
8
9
# Service Connection Settings
connectionType: workloadIdentityFederation
subscriptionId: $(AZURE_SUBSCRIPTION_ID)
resourceGroup: $(RESOURCE_GROUP_NAME)
servicePrincipal: auto-generated

# Required Permissions:
# - API Management Contributor
# - Resource Group Contributor (if creating resources)

Alternative: Service Principal with Certificate/Secret

1
2
3
4
# Only use if Workload Identity Federation is not available
connectionType: servicePrincipal
authenticationType: spnCertificate
# or: spnKey for secret-based authentication

3. Repository Permissions

Configure the build service account to create pull requests:

1
2
3
4
# Grant Contributor access to build service
Project Settings > Repositories > Security
Add: [Project Name] Build Service (organization-name)
Permissions: Contribute, Create branch, Create pull request

4. Environment Configuration

Set up approval gates for production deployments:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Environment Configuration
environments:
  - name: Development
    approvals: []
    
  - name: Production  
    approvals:
      - type: manual
        approvers: [api-team@company.com]
        timeout: 24h

5. Variable Groups

Create the apim-automation variable group:

1
2
3
4
5
6
# Required Variables
API_MANAGEMENT_SERVICE_NAME: "your-apim-instance"
APIOPS_RELEASE_VERSION: "latest"  
RESOURCE_GROUP_NAME: "your-resource-group"
REPOSITORY_NAME: "your-repo-name"
TARGET_FOLDER_NAME: "apim"

Pipeline Configuration Examples

Extractor Pipeline (run-extractor.yml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
trigger: none  # Manual execution only

pool:
  vmImage: 'ubuntu-latest'

variables:
- group: apim-automation

stages:
- stage: Extract
  displayName: 'Extract API Management Configuration'
  jobs:
  - job: ExtractAPIM
    displayName: 'Extract APIM Configuration'
    steps:
    
    - task: AzureCLI@2
      displayName: 'Run API Ops Extractor'
      inputs:
        azureSubscription: $(AZURE_SERVICE_CONNECTION)
        scriptType: 'bash'
        scriptLocation: 'inlineScript'
        inlineScript: |
          # Download and run extractor
          wget https://github.com/Azure/apiops/releases/latest/download/extractor.zip
          unzip extractor.zip -d extractor
          
          # Set permissions
          chmod +x extractor/extractor
          
          # Run extraction
          ./extractor/extractor \
            --sourceApimName $(API_MANAGEMENT_SERVICE_NAME) \
            --resourceGroupName $(RESOURCE_GROUP_NAME) \
            --fileFolder $(TARGET_FOLDER_NAME) \
            --commitAuthorName "API Ops Extractor" \
            --commitAuthorEmail "apiops@company.com"

Publisher Pipeline (run-publisher.yml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
trigger:
  branches:
    include:
    - main
  paths:
    include:
    - apim/*

pool:
  vmImage: 'ubuntu-latest'

variables:
- group: apim-automation

stages:
- stage: Validate
  displayName: 'Validate API Specifications'
  jobs:
  - job: SpectralValidation
    displayName: 'Run Spectral API Linting'
    steps:
    - task: NodeTool@0
      inputs:
        versionSpec: '16.x'
    
    - script: |
        npm install -g @stoplight/spectral-cli
        spectral lint "apim/apis/**/*.json" --ruleset .spectral.yml
      displayName: 'Validate OpenAPI Specs'

- stage: DeployDev
  displayName: 'Deploy to Development'
  dependsOn: Validate
  jobs:
  - deployment: PublishToDev
    environment: Development
    strategy:
      runOnce:
        deploy:
          steps:
          - task: AzureCLI@2
            displayName: 'Publish to Development'
            inputs:
              azureSubscription: $(AZURE_SERVICE_CONNECTION)
              scriptType: 'bash'
              scriptLocation: 'inlineScript'
              inlineScript: |
                # Run publisher for development
                ./publisher/publisher \
                  --targetApimName $(API_MANAGEMENT_SERVICE_NAME) \
                  --resourceGroupName $(RESOURCE_GROUP_NAME) \
                  --fileFolder $(TARGET_FOLDER_NAME) \
                  --configurationFile configuration.dev.yml

- stage: DeployProd
  displayName: 'Deploy to Production'
  dependsOn: DeployDev
  jobs:
  - deployment: PublishToProd
    environment: Production
    strategy:
      runOnce:
        deploy:
          steps:
          - task: AzureCLI@2
            displayName: 'Publish to Production'
            inputs:
              azureSubscription: $(AZURE_SERVICE_CONNECTION)
              scriptType: 'bash'
              scriptLocation: 'inlineScript'
              inlineScript: |
                # Run publisher for production
                ./publisher/publisher \
                  --targetApimName $(API_MANAGEMENT_SERVICE_NAME_PROD) \
                  --resourceGroupName $(RESOURCE_GROUP_NAME_PROD) \
                  --fileFolder $(TARGET_FOLDER_NAME) \
                  --configurationFile configuration.prod.yml

GitHub Implementation

Prerequisites and Setup

1. Download GitHub-Specific Toolkit

1
2
3
4
5
6
7
8
# Download GitHub Actions version
curl -L https://github.com/Azure/apiops/releases/latest/download/apiops-github.zip -o apiops-github.zip
unzip apiops-github.zip

# Available workflows:
├── .github/workflows/
│   ├── extract.yml        # Extractor workflow
│   └── publish.yml        # Publisher workflow

2. Azure Service Principal Setup

Create service principals for each environment:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Create service principal for development
az ad sp create-for-rbac --name "api-ops-dev" \
  --role "API Management Service Contributor" \
  --scopes /subscriptions/{subscription-id}/resourceGroups/{dev-rg} \
  --sdk-auth

# Create service principal for production  
az ad sp create-for-rbac --name "api-ops-prod" \
  --role "API Management Service Contributor" \
  --scopes /subscriptions/{subscription-id}/resourceGroups/{prod-rg} \
  --sdk-auth

3. GitHub Secrets Configuration

Store the service principal output as GitHub secrets:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Repository Secrets
AZURE_CREDENTIALS_DEV: |
  {
    "clientId": "xxx",
    "clientSecret": "xxx", 
    "subscriptionId": "xxx",
    "tenantId": "xxx"
  }

AZURE_CREDENTIALS_PROD: |
  {
    "clientId": "xxx",
    "clientSecret": "xxx",
    "subscriptionId": "xxx", 
    "tenantId": "xxx"
  }

4. Environment Configuration

Set up GitHub environments with protection rules:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# .github/environments/development.yml
name: Development
protection_rules: []

# .github/environments/production.yml  
name: Production
protection_rules:
  - type: required_reviewers
    required_reviewers:
      - api-team
  - type: wait_timer
    wait_timer: 5  # minutes

Note: Protection rules require GitHub Enterprise. For GitHub Pro/Team, use manual workflow triggers.

5. Workflow Permissions

Enable GitHub Actions to create pull requests:

1
2
3
4
5
# Repository Settings > Actions > General
permissions:
  contents: write
  pull-requests: write
  actions: read

GitHub Workflows Examples

Extractor Workflow (.github/workflows/extract.yml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
name: Extract API Management Configuration

on:
  workflow_dispatch:
    inputs:
      apimServiceName:
        description: 'APIM Service Name'
        required: true
      resourceGroupName:  
        description: 'Resource Group Name'
        required: true

jobs:
  extract:
    runs-on: ubuntu-latest
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Azure Login
      uses: azure/login@v1
      with:
        creds: ${{ secrets.AZURE_CREDENTIALS_DEV }}
        
    - name: Download API Ops Extractor
      run: |
        wget https://github.com/Azure/apiops/releases/latest/download/extractor.zip
        unzip extractor.zip -d tools
        chmod +x tools/extractor
        
    - name: Run Extractor
      run: |
        ./tools/extractor \
          --sourceApimName ${{ github.event.inputs.apimServiceName }} \
          --resourceGroupName ${{ github.event.inputs.resourceGroupName }} \
          --fileFolder apim \
          --commitAuthorName "API Ops Bot" \
          --commitAuthorEmail "apiops@company.com"
      env:
        AZURE_CLIENT_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).clientId }}
        AZURE_CLIENT_SECRET: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).clientSecret }}
        AZURE_SUBSCRIPTION_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).subscriptionId }}
        AZURE_TENANT_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).tenantId }}
        
    - name: Create Pull Request
      uses: peter-evans/create-pull-request@v5
      with:
        token: ${{ secrets.GITHUB_TOKEN }}
        commit-message: 'Extract API Management configuration'
        title: 'API Management Configuration Update'
        body: |
          ## API Management Configuration Extract
          
          This PR contains the latest configuration extracted from API Management instance: `${{ github.event.inputs.apimServiceName }}`
          
          ### Changes Include:
          - API definitions and specifications  
          - Backend configurations
          - Policy definitions
          - Product and subscription settings
          
          Please review and merge to sync the repository with the current APIM state.
        branch: apiops/extract-config
        delete-branch: true

Publisher Workflow (.github/workflows/publish.yml)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
name: Publish to API Management

on:
  push:
    branches:
      - main
    paths:
      - 'apim/**'
  pull_request:
    branches:
      - main
    paths:
      - 'apim/**'

jobs:
  validate:
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Setup Node.js
      uses: actions/setup-node@v3
      with:
        node-version: '18'
        
    - name: Install Spectral
      run: npm install -g @stoplight/spectral-cli
      
    - name: Validate OpenAPI Specifications
      run: |
        spectral lint "apim/apis/**/*.json" --ruleset .spectral.yml --format junit --output spectral-report.xml
        
    - name: Upload Spectral Report
      uses: actions/upload-artifact@v3
      if: always()
      with:
        name: spectral-report
        path: spectral-report.xml

  deploy-dev:
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    environment: Development
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Azure Login
      uses: azure/login@v1
      with:
        creds: ${{ secrets.AZURE_CREDENTIALS_DEV }}
        
    - name: Download API Ops Publisher
      run: |
        wget https://github.com/Azure/apiops/releases/latest/download/publisher.zip
        unzip publisher.zip -d tools
        chmod +x tools/publisher
        
    - name: Publish to Development
      run: |
        ./tools/publisher \
          --targetApimName ${{ vars.APIM_SERVICE_NAME_DEV }} \
          --resourceGroupName ${{ vars.RESOURCE_GROUP_NAME_DEV }} \
          --fileFolder apim \
          --configurationFile configuration.dev.yml
      env:
        AZURE_CLIENT_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).clientId }}
        AZURE_CLIENT_SECRET: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).clientSecret }}
        AZURE_SUBSCRIPTION_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).subscriptionId }}
        AZURE_TENANT_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_DEV).tenantId }}

  deploy-prod:
    runs-on: ubuntu-latest
    needs: deploy-dev
    environment: Production
    
    steps:
    - name: Checkout repository
      uses: actions/checkout@v4
      
    - name: Azure Login
      uses: azure/login@v1
      with:
        creds: ${{ secrets.AZURE_CREDENTIALS_PROD }}
        
    - name: Download API Ops Publisher  
      run: |
        wget https://github.com/Azure/apiops/releases/latest/download/publisher.zip
        unzip publisher.zip -d tools
        chmod +x tools/publisher
        
    - name: Publish to Production
      run: |
        ./tools/publisher \
          --targetApimName ${{ vars.APIM_SERVICE_NAME_PROD }} \
          --resourceGroupName ${{ vars.RESOURCE_GROUP_NAME_PROD }} \
          --fileFolder apim \
          --configurationFile configuration.prod.yml
      env:
        AZURE_CLIENT_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_PROD).clientId }}
        AZURE_CLIENT_SECRET: ${{ fromJson(secrets.AZURE_CREDENTIALS_PROD).clientSecret }}
        AZURE_SUBSCRIPTION_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_PROD).subscriptionId }}
        AZURE_TENANT_ID: ${{ fromJson(secrets.AZURE_CREDENTIALS_PROD).tenantId }}

Configuration Management Best Practices

Environment-Specific Configuration Files

API Ops supports environment-specific configurations to handle differences between Dev, Test, and Production environments.

Configuration Structure

apim/
├── apis/
├── backends/  
├── policies/
├── products/
├── configuration.dev.yml    # Development overrides
├── configuration.test.yml   # Test overrides  
└── configuration.prod.yml   # Production overrides

Development Configuration (configuration.dev.yml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# configuration.dev.yml
namedValues:
  - name: instrumentation-key
    value: "dev-app-insights-key"
    
backends:
  - name: api-todo-backend
    url: "https://api-dev.company.com"
    
diagnostics:
  - name: applicationinsights
    loggerId: "/subscriptions/{sub-id}/resourceGroups/dev-rg/providers/Microsoft.Insights/components/dev-appinsights"

Production Configuration (configuration.prod.yml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# configuration.prod.yml
namedValues:
  - name: instrumentation-key
    value: "prod-app-insights-key"
    
backends:
  - name: api-todo-backend
    url: "https://api-prod.company.com"
    
diagnostics:
  - name: applicationinsights  
    loggerId: "/subscriptions/{sub-id}/resourceGroups/prod-rg/providers/Microsoft.Insights/components/prod-appinsights"

Backend Configuration Example

Backend Definition (backends/api-todo-backend.yml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# backends/api-todo-backend.yml
name: api-todo-backend
title: "API Todo Backend Service"
description: "Backend service for Todo API operations"
url: "https://localhost:5000"  # Default/template URL
protocol: http
credentials:
  authorization:
    scheme: bearer
    parameter: "{{backend-auth-token}}"
proxy:
  url: "{{proxy-url}}"
  username: "{{proxy-username}}"
  password: "{{proxy-password}}"
tls:
  validateCertificateChain: true
  validateCertificateName: true

Policy Fragment Management

Correlation ID Policy Fragment (policies/correlation-id-fragment.xml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<!-- policies/correlation-id-fragment.xml -->
<fragment>
  <choose>
    <when condition="@(!context.Request.Headers.ContainsKey("correlation-id"))">
      <set-header name="correlation-id" exists-action="skip">
        <value>@(Guid.NewGuid().ToString())</value>
      </set-header>
    </when>
  </choose>
</fragment>

API Policy Using Fragment (apis/todo-api/policy.xml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- apis/todo-api/policy.xml -->
<policies>
  <inbound>
    <base />
    <include-fragment fragment-id="correlation-id-fragment" />
    <set-backend-service backend-id="api-todo-backend" />
    <rate-limit calls="100" renewal-period="60" />
    <quota calls="1000" renewal-period="86400" />
  </inbound>
  <backend>
    <base />
  </backend>
  <outbound>
    <base />
    <set-header name="X-Powered-By" exists-action="override">
      <value>Azure API Management</value>
    </set-header>
  </outbound>
  <on-error>
    <base />
    <trace source="api-error">
      <message>@($"Error occurred: {context.LastError.Message}")</message>
    </trace>
  </on-error>
</policies>

Advanced Features and Integration

Spectral API Linting Integration

Spectral Configuration (.spectral.yml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# .spectral.yml
extends: ["@stoplight/spectral/dist/rulesets/oas/index.json"]

rules:
  # Enforce API versioning
  api-version-required:
    description: "API must have version in path"
    given: "$.paths.*~"
    then:
      function: pattern
      functionOptions:
        match: "^/v[0-9]+/"

  # Enforce correlation ID header
  correlation-id-header:
    description: "All operations must accept correlation-id header"
    given: "$.paths.*.*.parameters[?(@.in === 'header')]"
    then:
      function: schema
      functionOptions:
        schema:
          type: object
          properties:
            name:
              enum: ["correlation-id", "x-correlation-id"]

  # Enforce response schemas
  response-schema-required:
    description: "All success responses must have schema"
    given: "$.paths.*.*.responses[?(@property.match(/^2/))]"
    then:
      field: "content.application/json.schema"
      function: truthy

  # Security requirements
  security-defined:
    description: "All operations must have security requirements"
    given: "$.paths.*.*"
    then:
      field: "security"
      function: truthy

Custom Spectral Rules for API Management

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# .spectral-apim.yml
extends: [".spectral.yml"]

rules:
  # API Management specific rules
  apim-policy-required:
    description: "APIs should have rate limiting policies"
    given: "$.paths.*.*"
    then:
      function: custom-apim-policy-check

  apim-backend-required:
    description: "APIs must reference a backend service"
    given: "$.info"
    then:
      function: custom-backend-reference-check

  apim-product-assignment:
    description: "APIs should be assigned to products"
    given: "$.info"
    then:
      function: custom-product-assignment-check

Visual Studio Code Extension Integration

For development productivity, use the Azure API Management extension for VS Code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// .vscode/settings.json
{
  "azure-api-management.subscriptionId": "${env:AZURE_SUBSCRIPTION_ID}",
  "azure-api-management.resourceGroup": "${env:RESOURCE_GROUP_NAME}",
  "azure-api-management.serviceName": "${env:API_MANAGEMENT_SERVICE_NAME}",
  
  // Auto-format policy files
  "files.associations": {
    "*.policy.xml": "xml"
  },
  
  // Spectral linting
  "spectral.enable": true,
  "spectral.rulesetFile": ".spectral.yml"
}

Monitoring and Observability

Application Insights Integration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# diagnostics/application-insights.yml
name: applicationinsights
alwaysLog: allErrors
httpCorrelationProtocol: W3C
verbosity: information
logClientIp: true
sampling:
  samplingType: fixed
  percentage: 100
frontend:
  request:
    headers: ["correlation-id", "user-agent"]
    body: 
      bytes: 1024
  response:  
    headers: ["content-type", "x-response-time"]
    body:
      bytes: 1024
backend:
  request:
    headers: ["correlation-id", "authorization"]
    body:
      bytes: 1024
  response:
    headers: ["content-type", "x-response-time"]  
    body:
      bytes: 1024

Custom Metrics and Alerts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# monitoring/custom-metrics.yml
customMetrics:
  - name: api-response-time
    description: "API response time in milliseconds"
    unit: milliseconds
    aggregationType: average
    
  - name: api-error-rate
    description: "API error rate percentage"
    unit: percentage
    aggregationType: average
    
alerts:
  - name: high-error-rate
    description: "Alert when error rate exceeds 5%"
    condition: "api-error-rate > 5"
    severity: critical
    
  - name: slow-response-time
    description: "Alert when response time exceeds 2 seconds"
    condition: "api-response-time > 2000"
    severity: warning

Hands-On Demo: Complete API Deployment

Step 1: Create a New API

Let’s walk through deploying a complete Todo API with versioning and policies.

OpenAPI Specification (apis/todo-api/specification.json)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
{
  "openapi": "3.0.3",
  "info": {
    "title": "Todo API",
    "description": "A simple Todo list API with correlation tracking",
    "version": "1.0.0",
    "contact": {
      "name": "API Team",
      "email": "api-team@company.com"
    }
  },
  "servers": [
    {
      "url": "https://api.company.com/v1",
      "description": "Production server"
    }
  ],
  "paths": {
    "/todos": {
      "get": {
        "summary": "Get all todos",
        "operationId": "getTodos",
        "parameters": [
          {
            "name": "correlation-id", 
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Correlation ID for request tracing"
          }
        ],
        "responses": {
          "200": {
            "description": "List of todos",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Todo"
                  }
                }
              }
            }
          },
          "400": {
            "description": "Bad request - missing correlation ID"
          }
        },
        "security": [
          {
            "apiKey": []
          }
        ]
      },
      "post": {
        "summary": "Create a new todo",
        "operationId": "createTodo",
        "parameters": [
          {
            "name": "correlation-id",
            "in": "header", 
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTodoRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Todo created successfully",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Todo"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Todo": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "title": {
            "type": "string"
          },
          "completed": {
            "type": "boolean"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CreateTodoRequest": {
        "type": "object",
        "required": ["title"],
        "properties": {
          "title": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          }
        }
      }
    },
    "securitySchemes": {
      "apiKey": {
        "type": "apiKey",
        "in": "header", 
        "name": "Ocp-Apim-Subscription-Key"
      }
    }
  }
}

Step 2: Configure Backend Services

Development Backend (configuration.dev.yml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Development environment backend configuration
backends:
  - name: api-todo-backend
    url: "https://todo-api-dev.company.com"
    protocol: https
    credentials:
      header:
        authorization: "Bearer {{dev-api-token}}"
    tls:
      validateCertificateChain: true
      
namedValues:
  - name: dev-api-token
    value: "dev-bearer-token-value"
    secret: true

Production Backend (configuration.prod.yml)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# Production environment backend configuration
backends:
  - name: api-todo-backend
    url: "https://todo-api-prod.company.com"
    protocol: https
    credentials:
      header:
        authorization: "Bearer {{prod-api-token}}"
    tls:
      validateCertificateChain: true
      
namedValues:
  - name: prod-api-token
    value: "prod-bearer-token-value"
    secret: true

Step 3: Deploy and Test

Execute the Pipeline

1
2
3
4
5
6
7
8
9
# For Azure DevOps
az pipelines run --name "API-Ops-Publisher" \
  --variables API_MANAGEMENT_SERVICE_NAME=your-apim-instance \
  --variables RESOURCE_GROUP_NAME=your-resource-group

# For GitHub Actions  
gh workflow run publish.yml \
  -f apimServiceName=your-apim-instance \
  -f resourceGroupName=your-resource-group

Verify Deployment

1
2
3
4
5
6
# Test the deployed API
curl -X GET "https://your-apim.azure-api.net/todo/v1/todos" \
  -H "Ocp-Apim-Subscription-Key: your-subscription-key" \
  -H "correlation-id: $(uuidgen)"

# Expected Response: 200 OK with todo list

Platform Comparison: Azure DevOps vs GitHub

Azure DevOps Advantages

Seamless Azure Integration

  • Native service connections with Workload Identity Federation
  • Integrated with Azure Active Directory
  • Built-in Azure resource management

Enterprise Features

  • Robust approval workflows and gates
  • Advanced pipeline templates and inheritance
  • Comprehensive audit and compliance tracking

Mature DevOps Toolchain

  • Integrated work item tracking
  • Built-in test management
  • Advanced reporting and analytics

GitHub Advantages

Developer-Friendly Experience

  • Familiar Git workflow and interface
  • Excellent code review capabilities
  • Strong community and ecosystem

Advanced Automation

  • Powerful GitHub Actions marketplace
  • Flexible workflow triggers and conditions
  • Easy integration with third-party tools

Cost Effectiveness

  • Generous free tier for public repositories
  • Competitive pricing for private repositories
  • No additional costs for basic CI/CD

Configuration Complexity Comparison

Azure DevOps

1
2
3
4
5
# Easier setup for Azure-native scenarios
- Service Connection: ✅ One-click setup (with proper permissions)
- Variables: ✅ Centralized variable groups
- Environments: ✅ Built-in approval gates
- Permissions: ⚠️ Requires build service configuration

GitHub

1
2
3
4
5
# More setup required but flexible
- Authentication: ⚠️ Manual service principal creation required
- Secrets: ✅ Easy secret management
- Environments: ⚠️ Protection rules require Enterprise license
- Permissions: ✅ Simple workflow permissions

Troubleshooting Common Issues

Azure DevOps Issues

Build Service Permissions Error

1
2
3
4
5
# Error: TF401027: You need the Git 'CreateBranch' permission
# Solution: Grant contributor access to build service
Project Settings > Repositories > Security
Add: [Project] Build Service ([Organization])
Permissions: ✓ Contribute, ✓ Create branch, ✓ Create pull request

Service Connection Authentication Failed

1
2
3
4
5
6
7
# Error: Forbidden - Invalid credentials
# Solutions:
1. Use Workload Identity Federation (recommended)
2. Verify service principal has proper RBAC roles:
   - API Management Service Contributor  
   - Resource Group Contributor
3. Check subscription and tenant IDs are correct

Variable Group Not Found

1
2
3
4
5
6
7
# Error: Could not find variable group 'apim-automation'
# Solution: Create variable group with required variables:
- API_MANAGEMENT_SERVICE_NAME
- APIOPS_RELEASE_VERSION  
- RESOURCE_GROUP_NAME
- REPOSITORY_NAME
- TARGET_FOLDER_NAME

GitHub Issues

Workflow Permissions Error

1
2
3
4
5
# Error: Resource not accessible by integration
# Solution: Enable workflow permissions
Settings > Actions > General > Workflow Permissions:
✓ Read and write permissions
✓ Allow GitHub Actions to create and approve pull requests

Authentication with Azure Failed

1
2
3
4
5
6
7
8
# Error: Azure CLI login failed  
# Solution: Verify Azure credentials secret format:
{
  "clientId": "service-principal-app-id",
  "clientSecret": "service-principal-secret", 
  "subscriptionId": "azure-subscription-id",
  "tenantId": "azure-tenant-id"
}

Environment Protection Rules Not Working

1
2
3
# Issue: No approval required for production deployment
# Solution: GitHub Enterprise required for protection rules
# Workaround: Use workflow_dispatch with manual triggers

Common API Ops Issues

Configuration File Syntax Errors

1
2
3
4
5
6
7
# ❌ Common mistake: Using tabs instead of spaces
backends:
	- name: api-backend  # Tab character causes YAML parsing error

# ✅ Correct: Use spaces for indentation  
backends:
  - name: api-backend   # 2-space indentation

Environment-Specific Values Not Applied

1
2
3
4
5
6
7
8
9
# Issue: Development URLs appearing in production
# Root Cause: Missing configuration.prod.yml file
# Solution: Create environment-specific configuration files

# Verify configuration files exist:
ls -la apim/
├── configuration.dev.yml   ✓
├── configuration.test.yml  ✓ 
└── configuration.prod.yml  ✓

Spectral Validation Failures

1
2
3
4
5
6
7
8
# Error: OpenAPI specification validation failed
# Common issues:
1. Missing required headers (correlation-id)
2. No security definitions
3. Missing response schemas
4. Invalid API versioning in paths

# Solution: Update OpenAPI spec to match Spectral rules

Best Practices and Recommendations

🔒 Security Best Practices

1. Secrets Management

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# ✅ Use Key Vault references for sensitive values
namedValues:
  - name: database-connection-string
    keyVault:
      secretIdentifier: "https://vault.vault.azure.net/secrets/db-conn-string"
      
# ❌ Never commit secrets in plain text
namedValues:
  - name: api-key
    value: "super-secret-key-123"  # Never do this!

2. Least Privilege Access

1
2
3
4
# Service Principal Permissions (minimum required)
- API Management Service Contributor  # For APIM operations
- Key Vault Secrets User             # For secret access (if using Key Vault)
- Reader                             # For resource group access

3. Network Security

1
2
3
4
5
6
# Configure IP restrictions for management access
management:
  restrictAccess: true
  allowedIPs:
    - "10.0.0.0/8"      # Corporate network
    - "192.168.1.0/24"  # VPN network

🚀 Performance Optimization

1. Optimize Pipeline Execution

1
2
3
4
5
6
7
8
# Use pipeline caching for faster builds
- task: Cache@2
  inputs:
    key: 'apiops | $(Agent.OS) | $(Build.SourceVersion)'
    path: $(Pipeline.Workspace)/.apiops-cache
    restoreKeys: |
      apiops | $(Agent.OS)
      apiops

2. Parallel Environment Deployments

1
2
3
4
5
6
7
8
# Deploy non-production environments in parallel
strategy:
  matrix:
    development:
      environment: 'dev'
    testing:
      environment: 'test'
  maxParallel: 2

📊 Monitoring and Observability

1. Custom Dashboards

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "title": "API Ops Deployment Dashboard",
  "widgets": [
    {
      "title": "Deployment Success Rate",
      "query": "traces | where customDimensions.pipeline == 'api-ops-publisher' | summarize SuccessRate = avg(toint(customDimensions.success)) by bin(timestamp, 1d)"
    },
    {
      "title": "API Response Times",
      "query": "requests | where name contains 'todo-api' | summarize avg(duration) by bin(timestamp, 5m)"
    }
  ]
}

2. Automated Testing Integration

1
2
3
4
5
6
7
8
# Add API testing to pipeline
- task: Newman@4
  displayName: 'Run API Tests'
  inputs:
    collectionFileSource: 'URL'
    collectionURL: 'https://api.company.com/postman/todo-api-tests.json'
    environmentFileSource: 'URL'
    environmentURL: 'https://api.company.com/postman/environments/$(Environment).json'

📝 Documentation Standards

1. API Documentation Templates

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# API Documentation Template

## Overview
Brief description of what this API does.

## Authentication
- **Type**: API Key
- **Header**: `Ocp-Apim-Subscription-Key`
- **Required**: Yes

## Rate Limiting
- **Calls**: 100 per minute
- **Quota**: 10,000 per day

## Headers
- `correlation-id` (required): UUID for request tracing
- `user-agent` (optional): Client identification

## Error Handling
All APIs return standardized error responses:

```json
{
  "error": {
    "code": "InvalidRequest",
    "message": "The correlation-id header is required",
    "correlationId": "12345678-1234-1234-1234-123456789012"
  }
}

## Future Enhancements and Roadmap

### Upcoming API Ops Features

#### **1. Advanced Policy Templates**
```yaml
# Coming soon: Parameterized policy templates
policies:
  - name: security-template
    parameters:
      - name: rateLimitCalls
        type: integer
        default: 100
      - name: authScheme  
        type: string
        default: "bearer"
    template: |
      <rate-limit calls="{{rateLimitCalls}}" renewal-period="60" />
      <validate-jwt header-name="Authorization" scheme="{{authScheme}}">
        <!-- JWT validation logic -->
      </validate-jwt>

2. Multi-Cloud Support

1
2
3
4
5
6
7
8
9
# Planned: Support for multiple cloud providers
environments:
  - name: azure-prod
    provider: azure
    apimInstance: "azure-apim-prod"
    
  - name: aws-prod  
    provider: aws
    apiGateway: "aws-gateway-prod"

3. Enhanced Observability

1
2
3
4
5
6
7
8
9
# Roadmap: Built-in observability and analytics
observability:
  traces:
    enabled: true
    samplingRate: 0.1
  metrics:
    customMetrics: ["response-time", "error-rate"]
  alerts:
    channels: ["teams", "slack", "email"]

Conclusion

API Ops represents a significant evolution in API management, bringing the proven practices of DevOps to the API development lifecycle. Whether you choose Azure DevOps or GitHub, both platforms provide robust capabilities for implementing API Ops with Azure API Management.

Key Takeaways

🎯 Automation First: API Ops eliminates manual configuration drift between environments 🔄 Git as Source of Truth: All API configurations versioned and auditable
🚀 Faster Deployments: Automated pipelines reduce deployment time from hours to minutes 🛡️ Improved Quality: Built-in validation with Spectral ensures API consistency 📊 Better Visibility: Clear audit trail of all API changes and deployments

Platform Decision Matrix

FactorAzure DevOpsGitHub
Azure Integration⭐⭐⭐⭐⭐ Native⭐⭐⭐ Good
Setup Complexity⭐⭐⭐⭐ Easy (with permissions)⭐⭐ Moderate
Enterprise Features⭐⭐⭐⭐⭐ Comprehensive⭐⭐⭐ Good
Developer Experience⭐⭐⭐ Good⭐⭐⭐⭐⭐ Excellent
Cost⭐⭐⭐ Moderate⭐⭐⭐⭐ Lower
Community⭐⭐⭐ Corporate⭐⭐⭐⭐⭐ Vibrant

Getting Started Checklist

Phase 1: Foundation (Week 1)

  • Choose platform (Azure DevOps vs GitHub)
  • Set up service connections/authentication
  • Configure repository permissions
  • Download and customize API Ops toolkit

Phase 2: Basic Implementation (Week 2-3)

  • Run extractor to baseline current state
  • Set up basic publisher pipeline
  • Configure environment-specific settings
  • Test deployment to development environment

Phase 3: Production Readiness (Week 4-6)

  • Implement approval workflows
  • Add Spectral validation rules
  • Set up monitoring and alerting
  • Create documentation and runbooks
  • Deploy to production with approval gates

Phase 4: Advanced Features (Ongoing)

  • Implement automated testing
  • Add custom policy fragments
  • Set up advanced monitoring dashboards
  • Create reusable pipeline templates

Call to Action

Start your API Ops journey today! Choose the platform that best fits your organization’s needs and begin with a simple pilot project. The investment in automation will pay dividends as your API portfolio grows.

Remember: API Ops is not just about tools - it’s about culture, collaboration, and continuous improvement in your API development lifecycle.


Resources and References

📚 Documentation

🛠️ Tools and Extensions

🎥 Learning Resources

🤝 Community

Ready to transform your API management with API Ops? Start with our step-by-step implementation guide and join the growing community of API Ops practitioners! 🚀

Comments & Discussion

Join the conversation! Share your thoughts and connect with other readers.