API Management
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. See the section bibliography for the reference material consulted while preparing these pages. |
Commands on this page are current for Azure CLI 2.90.0 (checked 2026-09-27); re-verify against az version and
the linked reference pages before relying on them in a pipeline. Where Azure CLI genuinely cannot provision a
resource shape yet — the v2 pricing tiers, below — the page shows the Bicep or az rest alternative instead.
Bookshelf fronts both HTTP services — catalog-api and orders-api, already running on cae-bookshelf
(Container Apps) with an AKS-hosted alternative on aks-bookshelf
(Deploying Microservices on AKS) — with a single API Management instance, apim-bookshelf. This page
covers what APIM is, how to provision and size it, how Bookshelf’s two APIs get imported, secured, cached, and
routed through it, and how to operate, observe, and eventually delete it.
Concepts: gateway, management plane, developer portal
Azure API Management is made up of three components, Azure-hosted and fully managed by default:
- Gateway (data plane)
-
Every client request reaches the gateway first. It authenticates the caller (subscription key, JWT, mutual TLS), enforces rate limits and quotas, applies whatever transformation a policy specifies, optionally serves a cached response, forwards the request to the right backend, and emits logs/metrics/traces for every call.
catalog-apiandorders-apinever see a request that the gateway’s policies would have rejected. - Management plane (control plane)
-
Where an API provider configures the service — import an API’s schema (OpenAPI, WSDL, OData, a running Azure compute resource, WebSocket, GraphQL, gRPC), group operations into products, attach policies, inspect analytics, and manage developer accounts. Reached through the Azure portal, Azure CLI, Azure PowerShell, a VS Code extension, or the management REST API directly.
- Developer portal
-
An automatically generated, customizable website where API consumers (app developers, partner integrators) discover APIs, read their documentation, try an operation from an interactive console, create an account, subscribe to a product to get a key, and pull their own usage analytics. It can be branded, extended with custom content, or self-hosted entirely.
A fourth, optional piece — the self-hosted gateway — lets the data plane run outside Azure (on aks-bookshelf,
for Bookshelf) while the management plane and developer portal stay in Azure; see [Self-hosted gateway on
AKS].
Tiers
APIM ships two families of pricing tiers plus a serverless one, and picking the wrong family is the single most common APIM design mistake — the v2 tiers are not simply "the classic tiers, faster": several classic capabilities (multi-region deployment, the self-hosted gateway, backup/restore) are currently unavailable on any v2 tier, while others (VNet integration on Standard v2, workspaces on every v2 tier) exist only there.
- Classic
-
Consumption,Developer,Basic,Standard,Premium. The original offering.Developeris an economical, no-SLA option for non-production use;Basic,Standard, andPremiumare production-ready;Premiumis the tier for enterprises needing private backends, multi-region deployments, availability zones, and the self-hosted gateway. - v2
-
Basic v2,Standard v2,Premium v2. A newer platform built for faster provisioning and scaling.Basic v2targets development/test;Standard v2is production-ready with outbound VNet integration to reach network-isolated backends;Premium v2adds full inbound+outbound VNet injection, availability zones, and higher scale limits, running its gateway on a dedicated App Service Environment. - Consumption
-
A serverless gateway billed per call (no monthly unit fee), scaling automatically to zero and back; designed for spiky or low-traffic APIs fronting serverless/microservice backends. It skips most of the policy/gateway feature set the other tiers share (see the matrix below) in exchange for that scale-to-zero billing model.
Feature matrix
| Feature | Consumption | Developer | Basic | Basic v2 | Standard | Standard v2 | Premium | Premium v2 |
|---|---|---|---|---|---|---|---|---|
VNet injection (full inbound+outbound isolation) |
No |
Yes |
No |
No |
No |
No |
Yes |
Yes |
VNet integration (outbound only, to network-isolated backends) |
No |
Yes |
No |
No |
No |
Yes |
Yes |
Yes |
Private endpoint (inbound) |
No |
Yes |
Yes |
No |
Yes |
Yes |
Yes |
Yes |
Self-hosted gateway |
No |
Yes (single node) |
No |
No |
No |
No |
Yes |
No |
Workspaces |
No |
No |
No |
Yes |
No |
Yes |
Yes |
Yes |
Multi-region deployment |
No |
No |
No |
No |
No |
No |
Yes |
No |
Availability zones |
No |
No |
No |
No |
No |
No |
Yes |
Yes |
Backup and restore |
No |
Yes |
Yes |
No |
Yes |
No |
Yes |
No |
SLA |
Yes |
No |
Yes |
Yes |
Yes |
Yes |
Yes |
Yes |
Two consequences worth planning around before picking a tier: an architecture that needs the self-hosted gateway
and the faster v2 provisioning/scaling model cannot have both today — self-hosted gateways are Developer- or
Premium-only — and multi-region deployment is exclusively a classic-Premium capability, so a v2-tier instance is
always single-region (workspace gateways can still be spread across regions independently; the core v2 service
itself cannot). Bookshelf runs apim-bookshelf on classic Developer for this page’s walkthroughs (no SLA,
cheapest way to exercise every policy and the self-hosted gateway); production Bookshelf environments would move
to classic Premium (self-hosted gateway, multi-region) or Standard v2 (no self-hosted gateway needed,
faster scaling, outbound VNet integration to reach cae-bookshelf privately) depending on which of those two
features actually matters.
Creating the instance
Provisioning any APIM tier takes a long time — commonly 20 to 45 minutes for classic tiers, faster but still
not instant for v2 — because the service allocates dedicated compute and configures its own gateway
infrastructure rather than attaching to an existing shared pool; do not block a CI pipeline step on az apim
create returning without either backgrounding it or budgeting for that wait.
In the Azure portal
-
Search for API Management services and select Create.
-
Basics:
$RG, region, resource nameapim-bookshelf, organization name, administrator email. -
Pricing tier: pick a tab — Classic lists Consumption/Developer/Basic/Standard/Premium; v2 lists Basic v2/Standard v2/Premium v2 — and select the tier from Tiers.
-
Review + create, then Create. The portal shows a deployment-in-progress notification for the full provisioning window above; the instance is unusable until it completes.
With the Azure CLI
az apim create provisions every classic tier (Consumption, Developer, Basic, Standard, Premium) but,
as of Azure CLI 2.90.0, its --sku-name parameter does not accept a v2-tier value — there is no BasicV2,
StandardV2, or PremiumV2 the CLI will create directly. A classic-tier instance for this page’s walkthroughs:
az apim create \
--name apim-bookshelf \
--resource-group "$RG" \
--location "$LOCATION" \
--sku-name Developer \
--sku-capacity 1 \
--publisher-email admin@bookshelf.example \
--publisher-name Bookshelf
Derived from Azure CLI reference — az apim create.
To provision a v2 tier, use Bicep, an ARM template, Terraform, or a direct az rest call against the management
REST API instead. A minimal Bicep resource for Standard v2:
resource apimBookshelf 'Microsoft.ApiManagement/service@2024-05-01' = {
name: 'apim-bookshelf'
location: location
sku: {
name: 'StandardV2'
capacity: 1
}
properties: {
publisherEmail: 'admin@bookshelf.example'
publisherName: 'Bookshelf'
}
}
deployed with az deployment group create --resource-group "$RG" --template-file apim.bicep, or, without a
template file at all, directly via az rest:
az rest \
--method put \
--url "https://management.azure.com/subscriptions/$(az account show --query id -o tsv)/resourceGroups/$RG/providers/Microsoft.ApiManagement/service/apim-bookshelf?api-version=2024-05-01" \
--body '{
"location": "'"$LOCATION"'",
"sku": {"name": "StandardV2", "capacity": 1},
"properties": {"publisherEmail": "admin@bookshelf.example", "publisherName": "Bookshelf"}
}'
Importing an API
Bookshelf’s catalog-api publishes an OpenAPI (Swagger) document at its own /v3/api-docs endpoint (Spring
Boot’s springdoc-openapi default); APIM imports it directly from that URL rather than a hand-maintained copy.
In the Azure portal
-
Open
apim-bookshelf→ APIs → + Add API → OpenAPI. -
OpenAPI specification: the
catalog-apiingress URL’s/v3/api-docspath. -
Display name
Catalog API, Namecatalog-api, API URL suffixcatalog. -
Create. APIM parses every operation from the spec and lists them under the new API.
With the Azure CLI
az apim api import \
--resource-group "$RG" \
--service-name apim-bookshelf \
--api-id catalog-api \
--path catalog \
--specification-format OpenApi \
--specification-url "https://$(az containerapp show --name catalog-api --resource-group "$RG" --query properties.configuration.ingress.fqdn -o tsv)/v3/api-docs" \
--display-name "Catalog API"
Derived from Azure CLI reference — az apim api import and Import an OpenAPI specification.
Products and subscriptions
Bookshelf’s APIs are not called anonymously: they are grouped into a product — Bookshelf Public API,
containing catalog-api and, once imported the same way, orders-api — and every product is either open
(no key needed) or protected (a subscription key required). Bookshelf’s product is protected, with subscription
approval left to automatic for the developer-portal self-service flow.
A caller that subscribes (from the developer portal, or provisioned directly by an administrator) gets a
primary and secondary subscription key, sent on every call in the Ocp-Apim-Subscription-Key header (or as
the subscription-key query parameter, discouraged since it can leak into logs and browser history). The
gateway rejects any call to a protected product’s operations that omits a valid key before the request reaches
any policy or backend.
In the Azure portal
-
Products → + Add: name
Bookshelf Public API, Requires subscription checked, Requires approval unchecked (automatic), addcatalog-apiandorders-api, Save and publish. -
A caller subscribes from the developer portal’s Products page, or an administrator creates a subscription directly under Subscriptions → + Add subscription, scoped to the product.
With the Azure CLI
az apim product covers the product and its API membership, but the CLI has no az apim subscription command
group, so the administrator-provisioned subscription is created with az rest against the
Microsoft.ApiManagement/service/subscriptions REST API instead. APIM_ID holds the instance’s resource ID and
is reused by the later az rest calls on this page.
APIM_ID="$(az apim show --name apim-bookshelf --resource-group "$RG" --query id -o tsv)"
az apim product create \
--resource-group "$RG" \
--service-name apim-bookshelf \
--product-id bookshelf-public-api \
--product-name "Bookshelf Public API" \
--subscription-required true \
--approval-required false \
--state published
az apim product api add \
--resource-group "$RG" \
--service-name apim-bookshelf \
--product-id bookshelf-public-api \
--api-id catalog-api
az rest \
--method put \
--url "https://management.azure.com${APIM_ID}/subscriptions/bookshelf-web?api-version=2024-05-01" \
--body '{
"properties": {
"displayName": "Bookshelf web front end",
"scope": "/products/bookshelf-public-api",
"state": "active"
}
}'
# The keys are not returned on GET -- read them with listSecrets
az rest \
--method post \
--url "https://management.azure.com${APIM_ID}/subscriptions/bookshelf-web/listSecrets?api-version=2024-05-01" \
--query primaryKey -o tsv
state is set to active explicitly because a subscription created through the REST API otherwise starts in the
submitted state and cannot call the product until it is approved.
Policies
A policy is an XML statement executed at one of four pipeline stages, applicable at global, workspace, product, API, or operation scope:
base inside any section inherits the policy from the next broader scope (operation → API → product →
global); omitting it replaces the inherited policy entirely for that scope.
Rate limit and quota
<policies>
<inbound>
<base />
<rate-limit-by-key calls="100" renewal-period="60"
counter-key="@(context.Subscription.Id)" />
<quota-by-key calls="10000" renewal-period="2592000"
counter-key="@(context.Subscription.Id)" />
</inbound>
</policies>
rate-limit-by-key throttles short bursts (100 calls per 60-second window here); quota-by-key enforces a
longer-window ceiling (10,000 calls per 30-day window) — both keyed per subscription so one caller’s traffic
never throttles another’s. rate-limit and quota (without -by-key) apply the same limits product- or
API-wide instead of per caller.
Derived from Rate limit by key policy and Set usage quota by key policy.
validate-jwt against Microsoft Entra ID
<policies>
<inbound>
<base />
<validate-jwt header-name="Authorization" failed-validation-httpcode="401"
failed-validation-error-message="Unauthorized. Access token is missing or invalid.">
<openid-config url="https://login.microsoftonline.com/<tenant-id>/v2.0/.well-known/openid-configuration" />
<audiences>
<audience><audience></audience>
</audiences>
<issuers>
<issuer>https://login.microsoftonline.com/<tenant-id>/v2.0</issuer>
</issuers>
<required-claims>
<claim name="roles" match="any">
<value>orders.read</value>
</claim>
</required-claims>
</validate-jwt>
</inbound>
</policies>
APIM fetches the tenant’s signing keys from the openid-config discovery document and caches them (refreshed
roughly hourly), so a key rotation on the Entra ID side needs no policy change. Bookshelf uses this in front of
orders-api operations that must only be called on behalf of a signed-in user, layered on top of the
subscription-key check above rather than instead of it; see
Identity and Access for the app-registration and token-acquisition side of this that
this policy validates against.
CORS
<policies>
<inbound>
<base />
<cors allow-credentials="false">
<allowed-origins>
<origin>https://www.bookshelf.example</origin>
</allowed-origins>
<allowed-methods>
<method>GET</method>
<method>POST</method>
</allowed-methods>
<allowed-headers>
<header>Content-Type</header>
<header>Ocp-Apim-Subscription-Key</header>
</allowed-headers>
</cors>
</inbound>
</policies>
Bookshelf’s static web front end (Storage Accounts/Front Door-served) calls catalog-api
and orders-api through APIM from browser JavaScript, which is what makes this policy necessary at all — a
server-to-server caller never needs it.
Derived from CORS policy.
Caching
<policies>
<inbound>
<base />
<cache-lookup vary-by-developer="false" vary-by-developer-groups="false"
downstream-caching-type="none" must-revalidate="true">
<vary-by-query-parameter>category</vary-by-query-parameter>
</cache-lookup>
</inbound>
<outbound>
<base />
<cache-store duration="60" />
</outbound>
</policies>
cache-lookup in inbound short-circuits straight to outbound on a hit, skipping the backend call entirely;
cache-store in outbound populates the cache for the next matching request. Bookshelf applies this to
catalog-api’s read-only book-listing operation (60-second cache, varied by the `category query parameter),
never to orders-api’s operations, which must always reach the backend. `Basic/higher tiers use the built-in
cache sized per the Feature matrix; every tier, including Consumption, can instead point at an external
Azure Managed Redis cache (Redis and Partner Databases on Azure) for a larger or shared cache.
Derived from Cache lookup policy and Cache store policy.
Backend routing: rewrite-uri and set-backend-service
<policies>
<inbound>
<base />
<set-backend-service backend-id="catalog-api-backend" />
<rewrite-uri template="/api/v1/books{path}" copy-unmatched-params="true" />
</inbound>
</policies>
set-backend-service points the request at a named backend entity instead of whatever URL the
imported OpenAPI spec recorded; rewrite-uri then adjusts the path APIM forwards, letting a public-facing path
(/catalog/books/{isbn}) map onto a differently shaped internal one
(/api/v1/books/{isbn}) without catalog-api or its clients needing to agree on a single path shape.
Derived from Set backend service policy and Rewrite URL policy.
set-header
<policies>
<outbound>
<base />
<set-header name="X-Powered-By" exists-action="delete" />
<set-header name="X-Correlation-Id" exists-action="override">
<value>@(context.RequestId.ToString())</value>
</set-header>
</outbound>
</policies>
Bookshelf strips backend-identifying response headers (exists-action="delete") and stamps every response with
APIM’s own request ID for correlation across catalog-api/orders-api logs and client bug reports.
Derived from Set HTTP header policy.
[[Named values]] == Named values with Key Vault-backed secrets
A named value is a reusable, policy-referenceable \{{name}\} placeholder — a plain string, or a secret
backed directly by an Azure Key Vault secret rather than stored in APIM at all. Bookshelf keeps `orders-api’s
partner-webhook signing key as a Key Vault-backed named value, so no policy XML or exported configuration ever
contains the raw secret value, matching the pattern on Key Vault and Secrets.
APIM’s system-assigned (or a chosen user-assigned) managed identity needs Key Vault Secrets User on
kv-bookshelf before a Key-Vault-backed named value will resolve; the identity is enabled once, then reused for
every named value.
In the Azure portal
-
APIs → Named values → + Add.
-
Name
orders-webhook-signing-key; toggle Secret; Value → Key Vault; selectkv-bookshelfand the secret; confirm the managed identity offered has (or is granted)Key Vault Secrets Useron the vault. -
Reference it from a policy as
\{{orders-webhook-signing-key}\}.
With the Azure CLI
az apim nv create only takes a literal --value (optionally marked --secret); it has no parameter for a Key
Vault reference. The identity and role assignment below use the CLI, and the Key Vault-backed named value itself
is created with az rest against the Microsoft.ApiManagement/service/namedValues REST API, setting
properties.keyVault.secretIdentifier. identityClientId is left out so APIM uses its system-assigned identity
(set it to a user-assigned identity’s client ID to use that one instead). The secret URI is unversioned, so APIM
picks up a rotated secret automatically.
az apim update \
--name apim-bookshelf \
--resource-group "$RG" \
--set identity.type=SystemAssigned
az role assignment create \
--assignee-object-id "$(az apim show --name apim-bookshelf --resource-group "$RG" --query identity.principalId -o tsv)" \
--assignee-principal-type ServicePrincipal \
--role "Key Vault Secrets User" \
--scope "$(az keyvault show --name kv-bookshelf --query id -o tsv)"
az rest \
--method put \
--url "https://management.azure.com${APIM_ID}/namedValues/orders-webhook-signing-key?api-version=2024-05-01" \
--body '{
"properties": {
"displayName": "orders-webhook-signing-key",
"secret": true,
"keyVault": {
"secretIdentifier": "https://kv-bookshelf.vault.azure.net/secrets/orders-webhook-signing-key"
}
}
}'
Backends
A backend is an entity that records a target URL plus its own TLS/credential settings and, optionally, a
circuit breaker — referenced from a policy with set-backend-service instead of an inline URL, so a backend
move (a new catalog-api revision’s URL, a cutover to the AKS-hosted deployment) is a single entity update
rather than an edit to every policy that used the old URL.
In the Azure portal
-
APIs → Backends → + Add.
-
Name
catalog-api-backend, Runtime URL thecatalog-apiContainer Apps ingress FQDN (Container Apps); enable a circuit breaker (trip after N consecutive 5xx, cool down before retrying) so acatalog-apioutage fails fast instead of piling up slow calls at the gateway. -
A second backend,
catalog-api-aks-backend, instead points at the KubernetesService/ingress FQDN exposing the same API onaks-bookshelf(Deploying Microservices on AKS) — useful for a staged cutover between the two hosting options, switching which backend a policy’sset-backend-servicetargets rather than re-importing the API.
With the Azure CLI
az apim backend create \
--resource-group "$RG" \
--service-name apim-bookshelf \
--backend-id catalog-api-backend \
--url "https://$(az containerapp show --name catalog-api --resource-group "$RG" --query properties.configuration.ingress.fqdn -o tsv)" \
--protocol http
Derived from Azure CLI reference — az apim backend and Backends in Azure API Management.
Versions and revisions
Revisions are non-breaking, work-in-progress copies of an existing API: create one, edit it (a policy change, a
new operation) without affecting the currently published traffic, test it against a ;rev=2-suffixed URL, and
either make it current (traffic cuts over) or discard it. Versions are for breaking changes the API’s own
consumers must opt into deliberately — v1 and v2 coexist as separately callable API entities, distinguished
by a versioning scheme (path segment, query parameter, or request header), each with its own revisions.
Bookshelf uses a revision for a routine catalog-api policy tweak (say, tightening the cache TTL) and reserves a
new version for the day `orders-api’s public contract actually changes shape (a renamed field, a removed
operation).
In the Azure portal
-
Open the API → the Revisions dropdown next to its name → + Add revision; edit the new revision; Test it at its own URL suffix; Make current when ready.
-
To add a version instead: + Add version on the API’s list entry; choose a versioning scheme (Path is the common default:
/v2/catalog/…); the new version starts as a copy and evolves independently.
With the Azure CLI
az apim api revision create \
--resource-group "$RG" \
--service-name apim-bookshelf \
--api-id catalog-api \
--api-revision 2 \
--api-revision-description "Tighten cache TTL to 30s"
# ... test at .../catalog;rev=2/books, then promote it:
az apim api release create \
--resource-group "$RG" \
--service-name apim-bookshelf \
--api-id catalog-api \
--api-revision 2 \
--notes "Promote revision 2 to current"
[[Self-hosted gateway on AKS]] == Self-hosted gateway on AKS
The managed gateway above always runs inside Azure. A self-hosted gateway is the same gateway runtime,
packaged as a Linux container, deployed wherever the API traffic actually needs to be handled — on
aks-bookshelf for Bookshelf, so calls between services already running in that cluster never leave it to reach
an Azure-hosted gateway and back. It still reports to, and is configured entirely from, `apim-bookshelf’s
management plane: policies, APIs, and products are authored once and pushed to every gateway, managed or
self-hosted, that serves them.
The self-hosted gateway is Developer or Premium tier only (single node on Developer, unlimited on Premium; see
Feature matrix) and is deployed to a Kubernetes cluster via its official Helm chart, authenticating back to
apim-bookshelf with a gateway token generated from the instance.
In the Azure portal
-
apim-bookshelf→ Deployment and infrastructure → Self-hosted gateways → + Add: nameaks-bookshelf-gateway, a location label, and the APIs it should serve (catalog-api,orders-api). -
Open the new gateway → Deployment: adjust the token Expiry (30 days at most) and Secret key, select Generate, and note the Token and Configuration URL for the Helm install below.
With az rest and Helm
The Azure CLI has no az apim gateway command group, so the gateway resource and its access token come from the
Microsoft.ApiManagement/service/gateways REST API via az rest (reusing APIM_ID from
Products and subscriptions). A gateway token lasts at most 30 days. The configuration URL the pod connects to
is the Configuration API v2 endpoint, https://<apim-name>.configuration.azure-api.net.
# Create the self-hosted gateway resource on apim-bookshelf
az rest \
--method put \
--url "https://management.azure.com${APIM_ID}/gateways/aks-bookshelf-gateway?api-version=2024-05-01" \
--body '{
"properties": {
"description": "Self-hosted gateway on aks-bookshelf",
"locationData": {"name": "aks-bookshelf"}
}
}'
# Serve catalog-api from it
az rest \
--method put \
--url "https://management.azure.com${APIM_ID}/gateways/aks-bookshelf-gateway/apis/catalog-api?api-version=2024-05-01" \
--body '{"properties": {"provisioningState": "created"}}'
# Generate a gateway token (30 days is the maximum expiry)
GATEWAY_TOKEN="$(az rest \
--method post \
--url "https://management.azure.com${APIM_ID}/gateways/aks-bookshelf-gateway/generateToken?api-version=2024-05-01" \
--body '{"keyType": "primary", "expiry": "'"$(date -u -d '+30 days' +%Y-%m-%dT%H:%M:%SZ)"'"}' \
--query value -o tsv)"
helm repo add azure-apim-gateway https://azure.github.io/api-management-self-hosted-gateway/helm-charts/
helm repo update
helm upgrade --install aks-bookshelf-gateway azure-apim-gateway/azure-api-management-gateway \
--namespace bookshelf \
--set gateway.configuration.uri="https://apim-bookshelf.configuration.azure-api.net" \
--set gateway.auth.key="GatewayKey ${GATEWAY_TOKEN}"
Derived from REST API — Gateway: Create Or Update, REST API — Gateway Api: Create Or Update, REST API — Gateway: Generate Token, Deploy a self-hosted gateway to Azure Kubernetes Service, and Deploy a self-hosted gateway to Kubernetes with Helm.
A self-hosted gateway on AKS is one concrete way to implement the general API-gateway/BFF pattern described on API Gateway and Backends for Frontends with a managed product rather than a hand-rolled Spring Cloud Gateway/YARP deployment; the broader question of which gateway/secret-manager/API-manager combination Bookshelf should standardize on across its Container Apps and AKS footprints is tracked separately in issue #184, not settled by this page.
Observability via Application Insights
apim-bookshelf sends request telemetry — dependency calls, response times, failed requests, and
Ocp-Apim-Trace-Location-driven detailed traces when explicitly enabled per call — to an Application Insights
resource, the same one catalog-api and orders-api already report to if they use Spring Cloud Azure’s
auto-instrumentation, giving a single distributed trace across the gateway hop and both backends.
In the Azure portal
-
APIs → Application Insights (under Diagnostic Logs, or per-API under Settings) → connect an existing Application Insights resource; a sampling percentage controls telemetry volume/cost.
-
Per API, Settings → Diagnostics → enable the Application Insights logger for that API specifically, or rely on the instance-wide default.
The portal connection above stores an Application Insights instrumentation key in the logger. Support for instrumentation-key ingestion ended on 31 March 2025, so Bookshelf connects with a connection string instead, which the portal can’t configure yet.
With az rest
The Azure CLI has no az apim logger or az apim api diagnostic commands. The logger
(Microsoft.ApiManagement/service/loggers) and the per-API diagnostic
(Microsoft.ApiManagement/service/apis/diagnostics) are created with az rest (reusing APIM_ID from
Products and subscriptions). The logger authenticates with the Application Insights connection string plus
APIM’s system-assigned identity, which needs the Monitoring Metrics Publisher role on appi-bookshelf.
APPI_ID="$(az monitor app-insights component show --app appi-bookshelf --resource-group "$RG" --query id -o tsv)"
APPI_CONNECTION_STRING="$(az monitor app-insights component show --app appi-bookshelf --resource-group "$RG" --query connectionString -o tsv)"
az role assignment create \
--assignee-object-id "$(az apim show --name apim-bookshelf --resource-group "$RG" --query identity.principalId -o tsv)" \
--assignee-principal-type ServicePrincipal \
--role "Monitoring Metrics Publisher" \
--scope "$APPI_ID"
az rest \
--method put \
--url "https://management.azure.com${APIM_ID}/loggers/appinsights-bookshelf?api-version=2024-05-01" \
--body '{
"properties": {
"loggerType": "applicationInsights",
"resourceId": "'"$APPI_ID"'",
"credentials": {
"connectionString": "'"$APPI_CONNECTION_STRING"'",
"identityClientId": "SystemAssigned"
}
}
}'
az rest \
--method put \
--url "https://management.azure.com${APIM_ID}/apis/catalog-api/diagnostics/applicationinsights?api-version=2024-05-01" \
--body '{
"properties": {
"loggerId": "'"$APIM_ID"'/loggers/appinsights-bookshelf",
"alwaysLog": "allErrors",
"sampling": {"samplingType": "fixed", "percentage": 100}
}
}'
Networking
By default every managed-gateway tier is reachable over the public internet; Developer/Basic/Standard/Standard v2
add private-endpoint support for inbound traffic, and Premium/Premium v2 additionally support full VNet
injection so the gateway itself gets a private IP inside vnet-bookshelf with no public inbound path at all
(Feature matrix). Standard v2 and Premium v2 separately support outbound VNet integration purely to reach
network-isolated backends (cae-bookshelf on an internal-only Container Apps environment,
`aks-bookshelf’s internal load balancer) while keeping the gateway’s own inbound side public — the two are
independent settings, not the same feature at two price points.
Scaling
Classic Developer/Basic/Standard/Premium scale in whole units, each unit a fixed capacity increment, added or
removed manually or via az apim scale / an Azure Monitor autoscale rule watching the Capacity metric; v2
tiers instead scale faster, in the same unit model, up to 10 units (Basic v2/Standard v2) or 30 units (Premium
v2). Consumption scales automatically with no unit concept at all, billed per million calls instead. Multi-region
deployment (classic Premium only) adds whole additional regions, each independently scaled and each able to serve
gateway traffic, backed by the same configuration; Bookshelf’s single-region apim-bookshelf does not use this.
Pricing model
Consumption bills per call (roughly the first million calls per month free, then per-million after); classic and v2 tiers bill per unit per hour regardless of call volume, with v2 units additionally bundling a monthly included call allowance before per-million overage charges apply. Storage, bandwidth, and the external-cache option are priced separately from the base unit/call charge. Current rates, which change independently of anything on this page, are on the API Management pricing page; that page — not this one — is the source of truth for sizing a real budget.
Soft delete and purge
Deleting apim-bookshelf (from the portal, the CLI, or the REST API on API version 2020-06-01-preview or
later) does not remove it immediately: like Key Vault (Key Vault and Secrets), it moves
to a soft-deleted state, and its name stays reserved. Left soft-deleted for more than 48 hours without being
recovered or purged, it is purged automatically; purging explicitly frees the name immediately instead of
waiting out that window.
Troubleshooting
-
A caller gets
401 Access denied due to missing subscription key— the request omittedOcp-Apim-Subscription-Key(orsubscription-key), or used a key for a subscription that does not include the called product; check the product/subscription mapping under Products and subscriptions, not thevalidate-jwtpolicy, which is a separate, additional check. -
validate-jwtrejects a token that looks valid — the token’saudorissclaim does not match the policy’s<audiences>/<issuers>exactly (a trailing slash orv1.0vs.v2.0endpoint mismatch is the most common cause), or the signing-key cache is stale after an unusual Entra ID key rollover; APIM’s Trace tool (developer portal Test console, orOcp-Apim-Trace-Locationheader) shows exactly which claim failed. -
A v2-tier deployment fails or a feature silently does not apply — confirm it is not one of the currently-unavailable-in-v2 features (multi-region, self-hosted gateway, backup/restore, Git-based configuration) rather than a misconfiguration; the v2 tiers overview page keeps the current list, since it changes as v2 catches up to classic feature-for-feature.
-
A named value stays empty or a policy referencing it fails — almost always the managed identity’s Key Vault role assignment is missing or scoped to the wrong vault; re-check [Named values with Key Vault-backed secrets], and confirm the identity type (system- vs. user-assigned) the named value was created with still exists and is enabled on the APIM instance.
-
A self-hosted gateway shows unhealthy or stops receiving configuration updates — the gateway token expired (they are time-limited, per [Self-hosted gateway on AKS]) or the pod cannot reach the configuration endpoint from inside
aks-bookshelf’s network; `kubectl logson the gateway pod surfaces the specific connection or auth error, and a new token from thegenerateTokenaz restcall (or the portal’s Deployment blade) resolves an expiry. -
catalog-apicalls succeed directly but fail through APIM — check the backend entity’s URL and circuit breaker state first (Backends): a tripped circuit breaker fails fast with a 5xx that looks like an APIM problem but is really the backend’s own recent failure rate.
Clean up
az group delete --name "$RG" --yes --no-wait
# apim-bookshelf survives resource-group deletion in its soft-deleted state for up to 48 hours --
# purge it explicitly to free the name immediately
az apim deletedservice purge --service-name apim-bookshelf --location "$LOCATION"
Related pages
-
Key Vault and Secrets covers Key Vault itself, including the soft-delete/purge model APIM instances follow the same way, and the RBAC roles a named value’s managed identity needs.
-
Identity and Access covers Microsoft Entra ID, app registrations, and token acquisition in depth; this page only covers the
validate-jwtpolicy that checks the resulting token. -
AKS Clusters and Deploying Microservices on AKS cover
aks-bookshelfcluster setup and workload identity in more depth than the self-hosted-gateway-focused summary above. -
Container Apps covers
cae-bookshelfand thecatalog-api/orders-apideployments that APIM’s backends point at. -
API Gateway and Backends for Frontends covers the general API gateway/BFF pattern APIM is one managed implementation of.
References
-
Microsoft Learn — Azure API Management: overview and key concepts
-
Microsoft Learn — Feature-based comparison of Azure API Management tiers
-
Microsoft Learn — Quickstart: Create an Azure API Management instance by using Bicep
-
Microsoft Learn — Set or edit policies in Azure API Management
-
Microsoft Learn — Protect an API in API Management using OAuth 2.0 and Microsoft Entra ID
-
Microsoft Learn — Use named values in Azure API Management policies
-
Microsoft Learn — Deploy a self-hosted gateway to Azure Kubernetes Service
-
Microsoft Learn — Deploy a self-hosted gateway to Kubernetes with Helm
-
Microsoft Learn — Integrate Azure API Management with Azure Application Insights
-
Microsoft Learn — Azure CLI reference: az apim deletedservice