Why is rate-limit-by-key policy not being inherited from Product group to an API?

RamenTofu 20 Reputation points
2026-06-10T17:36:37.1533333+00:00

Hello!

We have the following rate-limit-by-key policy applied on our product group for a simple rate limit.

<policies>
    <inbound>
        <base />
        <rate-limit-by-key calls="30" renewal-period="60" counter-key="@(context.Request.Headers.GetValueOrDefault("Authorization", context.Request.IpAddress))" />
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
    </outbound>
    <on-error>
        <base />
    </on-error>
</policies>

The following is a Dummy API that we have to test with policies with the following policies:

<policies>
    <inbound>
        <base />
        <cors>
            <allowed-origins>
                <origin>*</origin>
            </allowed-origins>
            <allowed-methods>
                <method>GET</method>
                <method>POST</method>
                <method>PUT</method>
                <method>DELETE</method>
                <method>PATCH</method>
            </allowed-methods>
            <allowed-headers>
                <header>*</header>
            </allowed-headers>
            <expose-headers>
                <header>*</header>
            </expose-headers>
        </cors>
        <!-- Determine which key is being used -->
        <set-variable name="rateLimitKeyType" value="@(
            context.Request.Headers.ContainsKey("Authorization") ? "Authorization" :
            context.Request.Headers.ContainsKey("X-Forwarded-For") ? "X-Forwarded-For" :
            "IP Address"
        )" />
        <set-variable name="rateLimitKeyValue" value="@(
            context.Request.Headers.GetValueOrDefault("Authorization",
                context.Request.Headers.GetValueOrDefault("X-Forwarded-For",
                    context.Request.IpAddress))
        )" />
        <!-- Apply rate limit -->
        <!--<rate-limit-by-key calls="30" renewal-period="60" counter-key="@(context.Request.Headers.GetValueOrDefault("Authorization",context.Request.Headers.GetValueOrDefault("X-Forwarded-For",context.Request.IpAddress)))" />-->
        <!-- <rate-limit-by-key calls="30" renewal-period="60" counter-key="@((string)context.Variables["rateLimitKeyValue"])" /> -->
        <return-response>
            <set-status code="200" reason="ok" />
            <set-header name="Content-Type" exists-action="override">
                <value>test</value>
            </set-header>
            <set-body>@{
            return new JObject(
                new JProperty("MESSAGE", "succress")
            ).ToString();
        }</set-body>
        </return-response>
    </inbound>
    <backend>
        <base />
    </backend>
    <outbound>
        <base />
    </outbound>
    <on-error>
		<!-- Policy to view what key caused the rate-limit to be reached -->
        <choose>
            <when condition="@(context.Response.StatusCode == 429)">
                <return-response>
                    <set-status code="429" reason="Too Many Requests" />
                    <set-header name="Content-Type" exists-action="override">
                        <value>test</value>
                    </set-header>
                    <set-body>@{
            return new JObject(
                new JProperty("error", "Rate limit exceeded (default)"),
                new JProperty("limit", "30 requests per 60 seconds"),
                new JProperty("keyType", context.Variables["rateLimitKeyType"]),
                new JProperty("keyValue", context.Variables["rateLimitKeyValue"]),
                new JProperty("Authorization", context.Request.Headers.GetValueOrDefault("Authorization", "missing"))
            ).ToString();
        }</set-body>
                </return-response>
            </when>
        </choose>
        <base />
    </on-error>
</policies>

The inbound of the API has the <base /> tag, so it should be inheriting the rate-limiting policy. Yet when doing a simple load test the policy is not working. When placing the rate-limit-by-key tag on it's own in the API policy (without the base tag), the rate limiting is working as intended (both in managed and self hosted gateways). What could be the reason that this is the case?

The API is placed in the product group, and the global policies are simple header transformation to mask data, and we are not using workspaces. Has anyone seen anything like this before? The documentation says that rate-limit-by-key is applicable to all scopes.

Azure API Management
Azure API Management

An Azure service that provides a hybrid, multi-cloud management platform for APIs.


Answer accepted by question author
Pravallika KV 18,850 Reputation points Microsoft External Staff Moderator
2026-06-10T20:32:11.6266667+00:00

Hi @RamenTofu ,

Thanks for reaching out to Microsoft Q&A.

You're correct in your observation, but it’s slightly more about subscription resolution than just runtime evaluation or the presence of a header.

It's not simply that policies are evaluated only when a header is present. The real reason is that product-scope policies are only applied when APIM can resolve a subscription context for the request.

If you are not sending a subscription key and not using an alternative mechanism like JWT-to-subscription mapping, then APIM does not resolve a subscription, and therefore it cannot determine the associated product. As a result, product policies are skipped entirely, and they won’t appear in the effective policy even if the API is linked to a product.

So, the API is not missing inheritance; rather no subscription resolution means the product scope is never entered during runtime, and only the global and API-level policies are executed.

Hope this helps!


If the resolution was helpful, kindly take a moment to click on User's imageand click on Yes for was this answer helpful. And, if you have any further query do let us know.

Was this answer helpful?

1 person found this answer helpful.
0 comments No comments

0 additional answers

Sort by: Most helpful

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.