← Back to Guides

API Management Policies Deep Dive

IntermediateAPI Management2026-03-14

Understanding the Policy Pipeline

Every API request in APIM passes through four policy sections:

<policies>
    <inbound>
        <!-- Applied before the request is forwarded to the backend -->
    </inbound>
    <backend>
        <!-- Applied just before the request is forwarded -->
    </backend>
    <outbound>
        <!-- Applied to the response before sending to the caller -->
    </outbound>
    <on-error>
        <!-- Applied when an error occurs at any stage -->
    </on-error>
</policies>

Essential Inbound Policies

Rate Limiting

<rate-limit calls="100" renewal-period="60" />

Limits callers to 100 calls per 60 seconds. Use rate-limit-by-key for per-user or per-IP limits.

IP Filtering

<ip-filter action="allow">
    <address-range from="10.0.0.0" to="10.0.0.255" />
</ip-filter>

JWT Validation

<validate-jwt header-name="Authorization" require-scheme="Bearer">
    <openid-config url="https://login.microsoftonline.com/{tenant}/.well-known/openid-configuration" />
    <required-claims>
        <claim name="aud" match="all">
            <value>{client-id}</value>
        </claim>
    </required-claims>
</validate-jwt>

CORS

<cors allow-credentials="true">
    <allowed-origins>
        <origin>https://example.com</origin>
    </allowed-origins>
    <allowed-methods>
        <method>GET</method>
        <method>POST</method>
    </allowed-methods>
    <allowed-headers>
        <header>Authorization</header>
        <header>Content-Type</header>
    </allowed-headers>
</cors>

Outbound Policies

Response Transformation

<set-body>@{
    var response = context.Response.Body.As<JObject>();
    response["source"] = "api-gateway";
    return response.ToString();
}</set-body>

Header Management

<set-header name="X-Request-Id" exists-action="skip">
    <value>@(context.RequestId.ToString())</value>
</set-header>

Error Handling Policies

<on-error>
    <set-header name="Content-Type" exists-action="override">
        <value>application/json</value>
    </set-header>
    <set-body>@{
        return new JObject(
            new JProperty("error", context.LastError.Message),
            new JProperty("requestId", context.RequestId)
        ).ToString();
    }</set-body>
</on-error>

Policy Expressions

Policy expressions are C# code snippets prefixed with @:

  • Single expression: @(context.Request.IpAddress)
  • Multi-statement: @{ var x = 1; return x.ToString(); }

Backend Policies

Forward Request

<forward-request timeout="30" follow-redirects="true" buffer-request-body="true" />

Set Backend Service

<set-backend-service base-url="https://backend-v2.example.com" />

Send Request (Fan-Out Pattern)

Call multiple backends and aggregate results:

<inbound>
    <send-request mode="new" response-variable-name="ordersResponse" timeout="20">
        <set-url>https://orders-api.example.com/orders</set-url>
        <set-method>GET</set-method>
    </send-request>
    <send-request mode="new" response-variable-name="customersResponse" timeout="20">
        <set-url>https://customers-api.example.com/customers</set-url>
        <set-method>GET</set-method>
    </send-request>
    <return-response>
        <set-status code="200" reason="OK" />
        <set-header name="Content-Type" exists-action="override">
            <value>application/json</value>
        </set-header>
        <set-body>@{
            var orders = ((IResponse)context.Variables["ordersResponse"]).Body.As<JArray>();
            var customers = ((IResponse)context.Variables["customersResponse"]).Body.As<JArray>();
            return new JObject(
                new JProperty("orders", orders),
                new JProperty("customers", customers)
            ).ToString();
        }</set-body>
    </return-response>
</inbound>

Policy Fragments

Reusable policy blocks that can be referenced across multiple APIs:

<!-- Define a fragment -->
<fragment>
    <rate-limit-by-key calls="100" renewal-period="60"
        counter-key="@(context.Subscription?.Key ?? context.Request.IpAddress)" />
    <set-header name="X-Request-Id" exists-action="skip">
        <value>@(context.RequestId.ToString())</value>
    </set-header>
</fragment>

Reference in a policy:

<inbound>
    <include-fragment fragment-id="standard-inbound" />
    <!-- Additional policies -->
</inbound>

Microsoft Reference: Policy fragments

Caching Policies

Response Caching

<inbound>
    <cache-lookup vary-by-developer="false"
                  vary-by-developer-groups="false"
                  downstream-caching-type="none">
        <vary-by-header>Accept</vary-by-header>
        <vary-by-query-parameter>status</vary-by-query-parameter>
    </cache-lookup>
</inbound>
<outbound>
    <cache-store duration="3600" />
</outbound>

Cache Value (Custom Key-Value)

<cache-lookup-value key="@("customer-" + context.Request.MatchedParameters["id"])"
                    variable-name="cachedCustomer" />
<choose>
    <when condition="@(context.Variables.ContainsKey("cachedCustomer"))">
        <return-response>
            <set-body>@((string)context.Variables["cachedCustomer"])</set-body>
        </return-response>
    </when>
</choose>

Transformation Policies

Rewrite URL

<rewrite-uri template="/api/v2/orders/{orderId}" />

Set Query Parameter

<set-query-parameter name="api-version" exists-action="override">
    <value>2024-01-01</value>
</set-query-parameter>

Find and Replace in Body

<find-and-replace from="http://internal-api.local" to="https://api.example.com" />

JSON to XML Conversion

<outbound>
    <choose>
        <when condition="@(context.Request.Headers.GetValueOrDefault("Accept","").Contains("xml"))">
            <json-to-xml apply="always" consider-accept-header="true" />
        </when>
    </choose>
</outbound>

Best Practices

  1. Apply policies at the appropriate scope — Global → Product → API → Operation (most specific wins)
  2. Use Named Values for configuration that changes between environments
  3. Test policies in the portal before deploying via CI/CD
  4. Use policy fragments to reuse common policy blocks across APIs
  5. Log errors with <trace> policy during development
  6. Use <base /> tag to inherit policies from parent scopes
  7. Keep policy expressions simple — complex C# logic should be in the backend
  8. Cache aggressively for read-heavy APIs to reduce backend load
  9. Use <send-request> for API composition and fan-out patterns
  10. Version your policies in source control alongside API definitions

Official Microsoft Resources