Deploying Azure API Management v2 with VNet injection, workspaces, and a workspace gateway: the parts nobody documented (part 1)

Deploying Azure API Management v2 with VNet injection, workspaces, and a workspace gateway: the parts nobody documented (part 1)

13 minutes

When Microsoft released the v2 tier of Azure API Management, it came with a promise: simpler networking, faster deployment, and a federation model that finally lets teams manage their own APIs without stepping on each other. On paper, it looks clean. In practice, getting it to work with VNet injection, workspaces, and a dedicated workspace gateway running in Internal mode, took me far longer than I expected.

Not because the feature doesn’t work. It does. But because the documentation is scattered, the resource types are confusing, and the ARM schema for workspace-gateway association is essentially undiscovered unless you know exactly where to look.

This post documents everything I figured out the hard way, with real Bicep code from my own implementation.

┌─────────────────────────────────────────────────────────┐
│  VNet: vnet-sah-hip-o                                   │
│                                                         │
│  ┌──────────────────────┐   ┌────────────────────────┐  │
│  │ sn-...-apim-o        │   │ sn-...-apim-gw-main-o  │  │
│  │ (Microsoft.Web/      │   │ (Microsoft.Web/        │  │
│  │  serverFarms)        │   │  hostingEnvironments)  │  │
│  │  /27                 │   │  /24                   │  │
│  │                      │   │                        │  │
│  │  ┌────────────────┐  │   │  ┌──────────────────┐  │  │
│  │  │ apimv2-sah-... │  │   │  │ apimgw-sah-...   │  │  │
│  │  │ PremiumV2      │  │   │  │ WorkspaceGateway │  │  │
│  │  │ External mode  │◄─┼───┼─►│ Internal mode    │  │  │
│  │  │                │  │   │  │ VIP: 10.0.9.4    │  │  │
│  │  │ ┌────────────┐ │  │   │  └──────────────────┘  │  │
│  │  │ │ workspace-A│ │  │   │          ▲             │  │
│  │  │ │ workspace-B│ │  │   │          │ configConn  │  │
│  │  │ └────────────┘ │  │   │          │             │  │
│  │  └────────────────┘  │   └──────────┼─────────────┘  │
│  └──────────────────────┘              │                │
│                                        │ Internal only  │
└────────────────────────────────────────┼────────────────┘
                                         │
                             VNet clients / ExpressRoute


The challenge, and why it’s harder than it looks

Let me describe what I was trying to achieve. I run a Hybrid Integration Platform (HIP) built on Azure API Management PremiumV2. I needed:

  • The APIM service itself injected into a VNet for private outbound routing to backends
  • Workspaces so different integration teams can manage their own APIs in isolation
  • workspace gateway in Internal mode, meaning the data plane is completely private, only reachable from within the VNet, not from the public internet

This is the setup Microsoft calls true physical isolation. One gateway, dedicated to one or more workspaces, with its own VNet subnet and its own private IP.

Simple enough in theory. But when I started building it, three things went wrong almost immediately.

First, the SKU name for a workspace gateway is not what you think. The resource type is Microsoft.ApiManagement/gateways, a standalone resource, not a child of the APIM service. And the SKU name changes based on your APIM tier: WorkspaceGatewayPremium for PremiumV2, WorkspaceGatewayStandard for the others. Deploy with the wrong name and you get a cryptic InvalidParameters: SkuType error after a 17-minute deployment.

Second, I needed to associate my workspaces with the gateway. The Azure portal makes it look like a simple wizard. But when I went to automate it, I couldn’t find the ARM resource type for the association. I tried Microsoft.ApiManagement/gateways/workspaces, the obvious candidate, and it returned a BadRequest with an empty message. Not helpful.

Third, virtually nothing I found online covered PremiumV2 specifically. Most blog posts and GitHub examples use the classic Premium tier (v1), which has completely different networking requirements. Port 3443 for the management plane. No delegation on the subnet. Different NSG rules. Copying those examples and applying them to v2 is a guaranteed way to waste an afternoon. The worst part, is even with some investigation and support from Github Copilot I couldn’t get it to work.

The horror, the pain…just to deploy a secure API management instance with isolated workspaces.
{
    "code": "ResourceDeploymentFailure",
    "target": "/subscriptions/[subscription id]/resourceGroups/rg-sah-hybrid-integration-platform-core-o/providers/Microsoft.Resources/deployments/apim-core",
    "message": "The resource write operation failed to complete successfully, because it reached terminal provisioning state 'Failed'.",
    "details": [
        {
            "code": "DeploymentFailed",
            "target": "/subscriptions/[subscription id]/resourceGroups/rg-sah-hybrid-integration-platform-core-o/providers/Microsoft.Resources/deployments/apim-core",
            "message": "At least one resource deployment operation failed. Please list deployment operations for details. Please see https://aka.ms/arm-deployment-operations for usage details.",
            "details": [
                {
                    "code": "BadRequest",
                    "message": ""
                },
                {
                    "code": "BadRequest",
                    "message": ""
                }
            ]
        }
    ]
}

The solution: step by step

Let me walk you through exactly how I built this with Bicep, end to end.

Prerequisites

Before you start, make sure you have:

  • An Azure DevOps pipeline or equivalent for Bicep deployments
  • An Azure subscription with quota for APIM PremiumV2 in your target region (workspace gateways are not available in all regions, check the regional availability list)
  • A VNet with two subnets ready to be created:
    • One for the APIM service itself (Microsoft.Web/serverFarms delegation, minimum /27)
    • One per workspace gateway (Microsoft.Web/hostingEnvironments delegation, minimum /24 recommended)

Step 1: Deploy the APIM v2 service with VNet injection

The APIM service itself is straightforward. The key thing to understand about v2 is that VNet integration is always External at the service level, even if you want private inbound access, that is handled by the workspace gateway, not by the APIM service. The service-level subnet only controls outbound routing to your backends.

To give an impression of what I wanted to achieve:

// All v2 tiers use 'External' for the service-level VNet integration.
// Private inbound access is handled by workspace gateways in their own subnet.
var virtualNetworkType = ApiManagementConfiguration.?networking != null ? 'External' : 'None'

resource apiManagement 'Microsoft.ApiManagement/service@2025-03-01-preview' = {
  name: ApiManagementConfiguration.name
  location: Location
  sku: {
    name: ApiManagementConfiguration.sku  // 'PremiumV2'
    capacity: 1
  }
  properties: {
    publisherName: ApiManagementConfiguration.publisherName
    publisherEmail: ApiManagementConfiguration.publisherEmail
    virtualNetworkType: virtualNetworkType
    virtualNetworkConfiguration: virtualNetworkType != 'None'
      ? { subnetResourceId: subnetResourceId }
      : null
  }
}

The subnet for the APIM service needs the Microsoft.Web/serverFarms delegation, not Microsoft.ApiManagement/service as you might expect from v1. This is one of the things that tripped me up initially.

The subnet needs to be delegated to Microsoft.Web/hostingEnvironments

Step 2: Create workspaces

Workspaces are child resources of the APIM service. They require StandardV2 or PremiumV2.

resource apiManagementWorkspaces 'Microsoft.ApiManagement/service/workspaces@2025-03-01-preview' = [
  for workspace in ApiManagementConfiguration.workspaces: {
    name: workspace.name
    parent: apiManagement
    properties: {
      displayName: workspace.displayName
    }
  }
]

A workspace is essentially a logical container, it holds its own APIs, products, subscriptions, and named values. Different teams get RBAC access scoped to their workspace only. The platform team retains oversight across all workspaces.

Important constraint: workspaces do not support managed identities. If you rely on Key Vault references in named values or authentication-managed-identity policies, you will need to work around that limitation.

Just showing the workspaces for reference

Step 3: Deploy the workspace gateway

This is where it gets interesting. The workspace gateway is not a child of the APIM service. It is a completely standalone Azure resource of type Microsoft.ApiManagement/gateways. It has its own resource group scope, its own subnet, its own hostname.

var workspaceGatewaySkuName = ApiManagementConfiguration.sku == 'PremiumV2'
  ? 'WorkspaceGatewayPremium'
  : 'WorkspaceGatewayStandard'

resource workspaceGateways 'Microsoft.ApiManagement/gateways@2025-09-01-preview' = [
  for gateway in ApiManagementConfiguration.gateways: {
    name: 'apimgw-${IntegrationContext.organizationName}-${IntegrationContext.workload.shortName}-${IntegrationContext.workload.integrationName}-${gateway.name}-${IntegrationContext.environmentLetter}'
    location: Location
    sku: {
      name: workspaceGatewaySkuName
      capacity: 1
    }
    properties: {
      virtualNetworkType: gateway.virtualNetworkType  // 'Internal' for private-only access
      backend: {
        subnet: {
          id: gateway.subnetResourceId
        }
      }
    }
    dependsOn: [apiManagement, apiManagementWorkspaces]
  }
]

Two things to pay close attention to here:

The SKU name is tier-dependent. For PremiumV2 use WorkspaceGatewayPremium. For BasicV2 or StandardV2 use WorkspaceGatewayStandard. Pass the wrong string and you get the InvalidParameters: SkuType error I mentioned earlier.

virtualNetworkType: 'Internal' means fully private. The gateway gets a private IP from your subnet (in my case, 10.0.9.4) and no public endpoint is created. API consumers must be inside the VNet, or connected via VPN/ExpressRoute, to reach the gateway. Once you deploy with Internal, you cannot change this without redeploying the gateway from scratch.

And the workspace gateway has been successfully deployed with an internal / private ip from the vnet

The gateway subnet needs a /24 (256 IPs) and the Microsoft.Web/hostingEnvironments delegation.

One thing that confused me during deployment: the gateway initially showed Internal VIP: 100.96.3.4. That address is in the RFC 6598 range (100.64.0.0/10) — Azure infrastructure space that appears on every gateway regardless of mode. It does not mean the gateway is in External mode. Confirmation that Internal mode is actually working is seeing a second VIP from your own subnet — in my case 10.0.9.4. Both addresses appear in the gateway overview once provisioning completes.

Step 4: Get the NSG rules right

Both subnets need NSG rules, and the requirements are different from the classic v1 tier. This is the section where I most wish someone had written it down earlier.

APIM service subnet (the Microsoft.Web/serverFarms one):

For v2, you do not need port 3443. That is a v1 management plane requirement that you will see in almost every blog post, ignore it for v2. The rules I use:

networkSecurityGroup: {
  rules: [
    {
      name: 'allow-vnet-inbound-tcp-port-443'
      description: 'Allow inbound HTTPS so VNet clients can reach the API Management gateway.'
      protocol: 'Tcp'
      sourcePortRange: '*'
      destinationPortRange: '443'
      sourceAddressPrefix: 'VirtualNetwork'
      destinationAddressPrefix: 'VirtualNetwork'
      access: 'Allow'
      priority: 1000
      direction: 'Inbound'
    }
    {
      name: 'allow-vnet-outbound-tcp-port-443'
      description: 'Allow APIM to call backends and Logic Apps trigger endpoints over HTTPS.'
      protocol: 'Tcp'
      sourcePortRange: '*'
      destinationPortRange: '443'
      sourceAddressPrefix: 'VirtualNetwork'
      destinationAddressPrefix: 'VirtualNetwork'
      access: 'Allow'
      priority: 1000
      direction: 'Outbound'
    }
    {
      name: 'allow-storage-outbound-tcp-port-443'
      description: 'Allow APIM to reach Azure Storage (rate limiting, sessions, configuration).'
      protocol: 'Tcp'
      sourcePortRange: '*'
      destinationPortRange: '443'
      sourceAddressPrefix: 'VirtualNetwork'
      destinationAddressPrefix: 'Storage'
      access: 'Allow'
      priority: 1100
      direction: 'Outbound'
    }
    {
      name: 'allow-entra-outbound-tcp-port-443'
      description: 'Allow APIM to reach Entra ID for OAuth2 token validation.'
      protocol: 'Tcp'
      sourcePortRange: '*'
      destinationPortRange: '443'
      sourceAddressPrefix: 'VirtualNetwork'
      destinationAddressPrefix: 'AzureActiveDirectory'
      access: 'Allow'
      priority: 1200
      direction: 'Outbound'
    }
    {
      name: 'allow-apimanagement-outbound-tcp-port-443'
      description: 'Allow APIM v2 to reach the management plane during activation and runtime.'
      protocol: 'Tcp'
      sourcePortRange: '*'
      destinationPortRange: '443'
      sourceAddressPrefix: 'VirtualNetwork'
      destinationAddressPrefix: 'ApiManagement'
      access: 'Allow'
      priority: 1300
      direction: 'Outbound'
    }
  ]
}

Workspace gateway subnet (the Microsoft.Web/hostingEnvironments one):

This subnet has two rules that I initially missed and that are not mentioned in Microsoft’s v2 networking documentation, I found them by studying an open-source reference implementation:

networkSecurityGroup: {
  rules: [
    {
      name: 'allow-gatewaymanager-inbound-tcp-port-443'
      description: 'Required: Azure Gateway Manager lifecycle management.'
      protocol: 'Tcp'
      sourcePortRange: '*'
      destinationPortRange: '443'
      sourceAddressPrefix: 'GatewayManager'
      destinationAddressPrefix: '*'
      access: 'Allow'
      priority: 1000
      direction: 'Inbound'
    }
    {
      name: 'allow-apimanagement-inbound-tcp-port-443'
      description: 'Required: APIM control plane pushes configuration to the gateway.'
      protocol: 'Tcp'
      sourcePortRange: '*'
      destinationPortRange: '443'
      sourceAddressPrefix: 'ApiManagement'
      destinationAddressPrefix: 'VirtualNetwork'
      access: 'Allow'
      priority: 1100
      direction: 'Inbound'
    }
    {
      name: 'allow-vnet-inbound-tcp-port-443'
      description: 'Allow VNet clients to reach the workspace gateway over HTTPS.'
      protocol: 'Tcp'
      sourcePortRange: '*'
      destinationPortRange: '443'
      sourceAddressPrefix: 'VirtualNetwork'
      destinationAddressPrefix: 'VirtualNetwork'
      access: 'Allow'
      priority: 1200
      direction: 'Inbound'
    }
    {
      name: 'allow-azureloadbalancer-inbound-tcp-port-65200-65535'
      description: 'Required: Azure infrastructure load balancer health probe.'
      protocol: 'Tcp'
      sourcePortRange: '*'
      destinationPortRange: '65200-65535'
      sourceAddressPrefix: 'AzureLoadBalancer'
      destinationAddressPrefix: 'VirtualNetwork'
      access: 'Allow'
      priority: 1300
      direction: 'Inbound'
    }
    {
      name: 'allow-vnet-outbound-tcp-port-443'
      description: 'Allow the workspace gateway to call backends over HTTPS.'
      protocol: 'Tcp'
      sourcePortRange: '*'
      destinationPortRange: '443'
      sourceAddressPrefix: 'VirtualNetwork'
      destinationAddressPrefix: 'VirtualNetwork'
      access: 'Allow'
      priority: 1000
      direction: 'Outbound'
    }
  ]
}

The GatewayManager rule allows Azure’s infrastructure to manage the gateway lifecycle. The ApiManagement rule allows the APIM service control plane to push configuration to the gateway. Without these two, the gateway cannot receive its workspace configuration after deployment.

Step 5: Associate workspaces with the gateway

This is the part that took me the longest to figure out. Searching the Microsoft docs for “associate workspace with gateway programmatically” gives you either the portal wizard or a vague mention that it is supported via REST API. No schema, no example, no resource type name.

The resource type you need is Microsoft.ApiManagement/gateways/configConnections. It is a child of the standalone workspace gateway resource, not of the APIM service. I found it documented on the ARM template reference page, and confirmed through a community Bicep implementation.

// Flatten the list of (gateway, workspace) pairs into individual configConnection resources.
// One configConnection per association.
var gatewayWorkspacePairs = flatten(map(
  range(0, length(ApiManagementConfiguration.gateways)),
  i => map(
    ApiManagementConfiguration.gateways[i].workspaceNames,
    workspaceName => {
      gatewayFullName: 'apimgw-${IntegrationContext.organizationName}-...-${ApiManagementConfiguration.gateways[i].name}-${IntegrationContext.environmentLetter}'
      workspaceName: workspaceName
    }
  )
))

resource gatewayConfigConnections 'Microsoft.ApiManagement/gateways/configConnections@2025-09-01-preview' = [
  for pair in gatewayWorkspacePairs: {
    name: '${pair.gatewayFullName}/${pair.workspaceName}'
    properties: {
      sourceId: resourceId(
        'Microsoft.ApiManagement/service/workspaces',
        ApiManagementConfiguration.name,
        pair.workspaceName
      )
      hostnames: []
    }
    dependsOn: [workspaceGateways, apiManagementWorkspaces]
  }
]

sourceId is the ARM resource ID of the workspace inside the APIM service. hostnames can be left as an empty array when using Internal mode, the gateway’s private hostname (apimgw-{name}-{hash}.gateway.{region}.azure-api.net resolving to your private IP) is auto-assigned by Azure.

After deployment, the workspace overview page in the portal shows the gateway as associated.

As you can see, the workspaces are now associated with the gateway!

And from the APIM perspective, the workspace is connected to the workspace gateway!

Putting it all together: the configuration model

In my implementation I use a single configuration object that flows through the entire Bicep module chain. The relevant section looks like this:

apiManagement: {
  sku: 'PremiumV2'
  workspaces: [
    { name: 'team-a', displayName: 'Team A' }
    { name: 'team-b', displayName: 'Team B' }
  ]
  gateways: [
    {
      name: 'main'
      addressSpace: '10.0.9.0/24'
      virtualNetworkType: 'Internal'
      workspaceNames: ['team-a', 'team-b']
    }
  ]
  networking: {
    addressSpace: '10.0.8.96/27'  // APIM service subnet
  }
}

From this, the Bicep layer computes subnet names, resource IDs, and gateway names, callers only supply address spaces and logical names.

I will also share my full api.management.bicep that rolls out the resources:

import * as generic from '../types/generic.types.bicep'
import * as module from '../types/module.types.bicep'

@description('Optional deployment datetime stamp.')
param DateTimeString string = utcNow()

@description('Input context for the integration.')
param IntegrationContext generic.integrationType

@description('Api Management configuration object with all information.')
param ApiManagementConfiguration module.apiManagementConfigurationType

@description('Optional location.')
param Location string = resourceGroup().location

// All v2 tiers (BasicV2, StandardV2, PremiumV2) use 'External' for VNet integration.
// The subnet (Microsoft.Web/hostingEnvironments delegation) provides outbound routing to
// private backends. Private inbound access on PremiumV2 is provided by workspace gateways,
// which are VNet-injected into their own dedicated subnet — not by setting 'Internal' here.
var virtualNetworkType = ApiManagementConfiguration.?networking != null ? 'External' : 'None'

var workspaceGatewaySkuName = ApiManagementConfiguration.sku == 'PremiumV2' ? 'WorkspaceGatewayPremium' : 'WorkspaceGatewayStandard'

// Flatten gateway × workspace into individual (gatewayFullName, workspaceName) pairs
// so we can deploy one configConnection resource per association.
var gatewayWorkspacePairs = flatten(map(
  range(0, length(ApiManagementConfiguration.gateways)),
  i => map(
    ApiManagementConfiguration.gateways[i].workspaceNames,
    workspaceName => {
      gatewayFullName: 'apimgw-${IntegrationContext.organizationName}-${IntegrationContext.workload.shortName}-${IntegrationContext.workload.integrationName}-${ApiManagementConfiguration.gateways[i].name}-${IntegrationContext.environmentLetter}'
      workspaceName: workspaceName
    }
  )
))

var subnetResourceId = ApiManagementConfiguration.?networking != null
  ? resourceId(
      ApiManagementConfiguration.networking!.virtualNetwork.resourceGroup.name,
      'Microsoft.Network/virtualNetworks/subnets',
      ApiManagementConfiguration.networking!.virtualNetwork.name,
      ApiManagementConfiguration.networking!.virtualNetwork.subnet.name
    )
  : ''

resource apiManagement 'Microsoft.ApiManagement/service@2025-03-01-preview' = {
  name: ApiManagementConfiguration.name
  location: Location
  tags: {
    Environment: IntegrationContext.environmentLetter
    WorkloadName: IntegrationContext.workload.shortName
    DeploymentStamp: DateTimeString
  }
  identity: {
    type: 'SystemAssigned'
  }
  sku: {
    name: ApiManagementConfiguration.sku
    capacity: 1
  }
  properties: {
    publisherName: ApiManagementConfiguration.publisherName
    publisherEmail: ApiManagementConfiguration.publisherEmail
    virtualNetworkType: virtualNetworkType
    virtualNetworkConfiguration: virtualNetworkType != 'None'
      ? { subnetResourceId: subnetResourceId }
      : null
  }
}

// Workspaces: StandardV2 and PremiumV2 only.
// The platform.core.bicep layer passes an empty array for BasicV2.
resource apiManagementWorkspaces 'Microsoft.ApiManagement/service/workspaces@2025-03-01-preview' = [
  for workspace in ApiManagementConfiguration.workspaces: {
    name: workspace.name
    parent: apiManagement
    properties: {
      displayName: workspace.displayName
    }
  }
]

// Workspace gateways: PremiumV2 only.
// Each gateway is VNet-injected into its own dedicated subnet (delegation: Microsoft.ApiManagement/service)
// and can serve up to 5 workspaces. The platform.core.bicep layer passes an empty array for
// BasicV2 and StandardV2.
// dependsOn ensures SKU validation runs against a healthy, fully-provisioned service.
resource workspaceGateways 'Microsoft.ApiManagement/gateways@2025-09-01-preview' = [
  for gateway in ApiManagementConfiguration.gateways: {
    name: 'apimgw-${IntegrationContext.organizationName}-${IntegrationContext.workload.shortName}-${IntegrationContext.workload.integrationName}-${gateway.name}-${IntegrationContext.environmentLetter}'
    location: Location
    tags: {
      Environment: IntegrationContext.environmentLetter
      WorkloadName: IntegrationContext.workload.shortName
      DeploymentStamp: DateTimeString
    }
    sku: {
      name: workspaceGatewaySkuName
      capacity: 1
    }
    properties: {
      virtualNetworkType: gateway.virtualNetworkType
      backend: {
        subnet: {
          // subnetResourceId is built with resourceId() in platform.core.bicep; the linter cannot trace it through the config type.
          #disable-next-line use-resource-id-functions
          id: gateway.subnetResourceId
        }
      }
    }
    dependsOn: [apiManagement, apiManagementWorkspaces]
  }
]


// One configConnection per (gateway, workspace) pair — this is the ARM resource that associates
// an APIM service workspace with a standalone workspace gateway for dedicated data-plane routing.
resource gatewayConfigConnections 'Microsoft.ApiManagement/gateways/configConnections@2025-09-01-preview' = [
  for pair in gatewayWorkspacePairs: {
    name: '${pair.gatewayFullName}/${pair.workspaceName}'
    properties: {
      sourceId: resourceId('Microsoft.ApiManagement/service/workspaces', ApiManagementConfiguration.name, pair.workspaceName)
      hostnames: []
    }
    dependsOn: [workspaceGateways, apiManagementWorkspaces]
  }
]

resource activityLogHealthAlert 'Microsoft.Insights/activityLogAlerts@2026-01-01' = {
  name: 'al-${IntegrationContext.organizationName}-${IntegrationContext.workload.shortName}-${IntegrationContext.workload.integrationName}-apim-health-${IntegrationContext.environmentLetter}'
  location: 'global'
  properties: {
    scopes: [
      apiManagement.id
    ]
    condition: {
      allOf: [
        {
          field: 'category'
          equals: 'ResourceHealth'
        }
        {
          anyOf: [
            {
              field: 'resourceType'
              equals: 'Microsoft.ApiManagement/service'
            }
          ]
        }
      ]
    }
    actions: {
      actionGroups: [
        {
          actionGroupId: ApiManagementConfiguration.actionGroupPlatformId
        }
      ]
    }
    enabled: bool(ApiManagementConfiguration.alerts.enabled)
    description: 'Resource Health waarschuwing voor ${apiManagement.name}'
  }
}

output apiManagement module.defaultWithPrincipalOutputType = {
  resourceName: apiManagement.name
  resourceId: apiManagement.id
  systemAssignedPrincipalId: apiManagement.identity.principalId
}

Conclusion and takeaways

Deploying APIM PremiumV2 with workspace isolation is absolutely achievable through Bicep, but it requires piecing together information from multiple sources, and there are a few traps along the way.

The things I would tell myself at the start of this journey:

  • Microsoft.ApiManagement/gateways is not a child of the APIM service. It is a standalone resource deployed independently to the same resource group.
  • The SKU name is tier-dependent. Use WorkspaceGatewayPremium for PremiumV2, WorkspaceGatewayStandard otherwise.
  • Do not copy v1 NSG rules for v2. Port 3443 is not needed. The workspace gateway subnet needs GatewayManager and ApiManagement inbound on 443 — that detail is not in the official v2 documentation.
  • The association resource is Microsoft.ApiManagement/gateways/configConnections. Not gateways/workspaces (which returns BadRequest), not a pipeline script — a proper ARM resource with sourceId pointing to the APIM service workspace.
  • Internal VNet mode means private-only. Once deployed, you cannot change the virtualNetworkType. Plan before you deploy.

Note: having multiple workspaces in a single workspace gateway requires some additional effort from an API perspective. By default all workspaces’ base addresses will point to the gateway URL and therefore it is advisable to have teams using an infix/suffix to distinguish URLs.

Be aware that if you have multiple workspaces on a single workspace gateway, the base URL will be the same. In order to distinguish APIs from different workspaces, use an suffix/infix

All the Bicep code shown in this post is part of my own code for a Hybrid Integration Platform, feel free to use it as a reference for your own implementation.


End note: The workspace gateway feature and the configConnections resource type are still on preview API versions (2025-09-01-preview at the time of writing). This means the schema could change and certain behaviors may still evolve. I will keep this post updated as the feature matures toward general availability.


Leave a Reply

Your email address will not be published. Required fields are marked *