Snippet
API Management policy that keeps the trace and drops caller baggage
A complete front-gateway policy: CORS for W3C headers, untrusted baggage removed, per-caller rate limits, and privacy-safe hop markers.
AI-generated, human-reviewed: the first draft was generated by an AI agent; I fact-checked, tested, and edited it. AI use policy
What it does#
Lets the browser send traceparent and tracestate through CORS, deletes caller-supplied
baggage at the trust boundary, rate-limits each caller by IP address, forwards to your
API, and writes short hop markers to Application Insights. The trace headers pass through
untouched.
You need#
- API Management on a classic tier that supports
rate-limit-by-key(Developer, Basic, Standard, or Premium; not Consumption) - Two named values:
spa-originandagent-api-backend-url - The API’s Application Insights diagnostics enabled with the W3C correlation protocol and an Application Insights logger at verbosity “information”
Where it goes#
Paste it as the API’s policy (all operations) in API Management.
<!-- SPDX-License-Identifier: MIT -->
<!-- Copyright (c) 2026 Sarang Datye -->
<!--
Front gateway policy for a browser app calling an agent API.
Named values to create in API Management:
{{spa-origin}} the browser app's origin, for example https://localhost:5173
{{agent-api-backend-url}} the agent API's base URL
This policy does not set or overwrite traceparent or tracestate. API Management's built-in
W3C correlation, enabled in the API's Application Insights diagnostics, propagates them.
The <trace> elements add privacy-safe hop markers such as [SPA-TO-APIM] to Application
Insights. They use static messages and APIM operation metadata; never caller-controlled
paths, headers, query strings, or bodies. They need the Application Insights logger with
verbosity "information".
-->
<policies>
<inbound>
<base />
<trace source="application_apim_1" severity="information">
<message>@("[SPA-TO-APIM] Request received from SPA")</message>
<metadata name="operation" value="@(context.Operation.Name)" />
</trace>
<cors allow-credentials="false">
<allowed-origins>
<origin>{{spa-origin}}</origin>
</allowed-origins>
<allowed-methods>
<method>GET</method>
<method>POST</method>
<method>DELETE</method>
<method>OPTIONS</method>
</allowed-methods>
<allowed-headers>
<header>Content-Type</header>
<header>traceparent</header>
<header>tracestate</header>
</allowed-headers>
</cors>
<!--
W3C baggage from a caller is untrusted. AgentApi would copy it onto spans and relay it to
the AI gateway, so it is dropped here at the trust boundary. traceparent/tracestate pass
through untouched.
-->
<set-header name="baggage" exists-action="delete" />
<!--
Per-user rate limit, keyed on the caller's IP. It sits here (not in AgentApi) because
AgentApi only ever sees APIM's IP, so a per-IP limit there is one bucket shared by all
users. Placed after cors so browser preflights are answered without being counted.
-->
<choose>
<when condition="@(context.Request.Url.Path.EndsWith("/api/feedback"))">
<rate-limit-by-key calls="60" renewal-period="60" counter-key="@("feedback:" + context.Request.IpAddress)" />
</when>
<otherwise>
<rate-limit-by-key calls="30" renewal-period="60" counter-key="@("chat:" + context.Request.IpAddress)" />
</otherwise>
</choose>
<set-backend-service base-url="{{agent-api-backend-url}}" />
<trace source="application_apim_1" severity="information">
<message>@("[APIM-TO-API] Forwarding request to AgentApi backend")</message>
<metadata name="operation" value="@(context.Operation.Name)" />
</trace>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
<trace source="application_apim_1" severity="information">
<message>@("[API-TO-APIM] Response received from AgentApi")</message>
</trace>
<trace source="application_apim_1" severity="information">
<message>@("[APIM-TO-SPA] Returning response to SPA caller")</message>
</trace>
</outbound>
<on-error>
<base />
</on-error>
</policies>
Check that it works#
Send a non-browser request with curl that includes both traceparent and baggage
headers. In Application Insights, the [SPA-TO-APIM] and [APIM-TO-API] markers share the
request’s operation ID, and your API never receives the baggage header. Browser CORS still
allows only Content-Type, traceparent, and tracestate; baggage is deliberately not an
allowed browser request header.