agentleFS
Azure-SamplesAzure-Samples/Apim-Samples/.github/skills/apim-policies/SKILL.md

apim-policies

Skill100 starsChanged 17 months agoMITView on GitHub

Guide for creating Azure API Management (APIM) XML policies. Use when users want to create, modify, or understand APIM policies including inbound/outbound processing, authentication, rate limiting, caching, transformations, and policy expressions. This skill provides policy syntax, examples, and C# policy expressions for request/response manipulation.

We split this skill into the rules it gives an agent

13 rules. About 0 are common, 9 are distinctive, and 4 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈9

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 4

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Do not add policy tooling or package installation that bypasses the repository's eight-day release waiting period.

    line 10
  2. Very specific:

    When this workflow creates or updates Markdown, follow .markdownlint.json and .github/markdown.instructions.md without flattening semantic …

    line 12
  3. Distinctive:

    Every APIM policy document follows this structure:

    line 16
  4. Distinctive:

    Keep policy XML readable as its control flow grows:

    line 43
  5. Distinctive:

    Add concise XML comments before non-obvious policy blocks to explain their intent, especially retry classification, fallback handling, …

    line 45
  6. Distinctive:

    Keep related policies together under a clear comment instead of allowing one uninterrupted block of XML.

    line 48
  7. Very specific:

    When retrying a backend request, make <retry> the sole direct child of <backend> and place <forward-request> plus any policies that must …

    line 55
  8. Very specific:

    When a custom backend policy is not needed, keep <base /> as the only direct child:

    line 66
  9. Distinctive:

    Use single quotes around XML attribute values that contain policy expressions.

    line 179
  10. Very specific:

    Keep ordinary non-expression XML attributes double-quoted.

    line 185
  11. Distinctive:

    Before using a type or member in a policy expression, verify it appears on the official allow-list.

    line 258
  12. Distinctive:

    When a member you need is not allowed, refactor to an equivalent that is.

    line 267
  13. Distinctive:

    If you are unsure whether a member is allowed, fetch the allowed types table and confirm before writing the expression.

    line 276

≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

APIM Policies

This skill provides guidance for creating Azure API Management XML policies.

Do not add policy tooling or package installation that bypasses the repository's eight-day release waiting period. Use the locked dependency workflow and verify release ages before running new tooling.

When this workflow creates or updates Markdown, follow .markdownlint.json and .github/markdown.instructions.md without flattening semantic structure. Before completion, run npx --no-install markdownlint-cli2 "**/*.md" "#**/.venv/**" "#**/node_modules/**" from the repository root and require zero violations.

Policy Document Structure

Every APIM policy document follows this structure:

<policies>
    <inbound>
        <base />
        <!-- Policies applied to incoming requests -->
    </inbound>
    <backend>
        <base />
        <!-- Policies applied before forwarding to backend -->
    </backend>
    <outbound>
        <base />
        <!-- Policies applied to outgoing responses -->
    </outbound>
    <on-error>
        <base />
        <!-- Policies applied when errors occur -->
    </on-error>
</policies>

The <base /> element inherits policies from parent scopes (Global → Product → API → Operation).

Readability and Semantic Layout

Keep policy XML readable as its control flow grows:

  • Add concise XML comments before non-obvious policy blocks to explain their intent, especially retry classification, fallback handling, cache behavior, and error normalization. Do not narrate self-explanatory individual statements.
  • Separate semantic phases with a blank line, such as initialization, routing and authentication, retry tracking, telemetry, response classification, and response rendering.
  • Break long policy elements across lines and place one attribute per line when that makes conditions or configuration easier to scan. Preserve the established indentation and policy execution order.
  • Keep related policies together under a clear comment instead of allowing one uninterrupted block of XML. Use comments and whitespace for structure; do not add wrapper policies that change APIM behavior solely for visual grouping.
  • Format multi-statement @{} expressions like ordinary C# blocks, with blank lines between distinct calculations or validation phases. Keep short @() expressions inline when they remain easy to read.

Backend Section Cardinality (CRITICAL)

The <backend> section may contain only one direct child policy. APIM rejects a policy during deployment when <backend> contains sibling policies such as <retry> followed by <choose>, or <base /> followed by <retry>.

When retrying a backend request, make <retry> the sole direct child of <backend> and place <forward-request> plus any policies that must execute on every attempt inside <retry>. Move terminal fallback handling to <on-error> or <outbound> as appropriate.

<backend>
    <retry count="2" interval="1" first-fast-retry="true"
        condition='@(context.Response.StatusCode == 429 || context.Response.StatusCode &gt;= 500)'>
        <forward-request timeout="60" buffer-request-body="true" />
    </retry>
</backend>

When a custom backend policy is not needed, keep <base /> as the only direct child:

<backend>
    <base />
</backend>

Policy Categories Quick Reference

CategoryCommon PoliciesSection
Authenticationauthentication-managed-identity, validate-azure-ad-token, validate-jwtinbound
Rate Limitingrate-limit-by-key, quota-by-keyinbound
Cachingcache-lookup, cache-storeinbound/outbound
Routingset-backend-service, forward-request, retryinbound/backend
Transformationset-header, set-body, set-variable, rewrite-uriany
Control Flowchoose, return-response, retry, waitany

Essential Policies

Set Backend Service

Route requests to a specific backend:

<set-backend-service backend-id="my-backend" />

Forward Request Timeout

Set timeout="60" explicitly on every forward-request policy. This bounds each backend attempt to 60 seconds, including each attempt nested inside a retry policy.

Use a different timeout only when measured workload behavior requires it, such as a documented long-running synchronous backend contract. Record the reason next to the policy and account for the total worst-case duration across the initial request and all retries. Do not omit the attribute to rely on the platform default.

<forward-request timeout="60" />

Authentication with Managed Identity

Authenticate to Azure services using APIM's managed identity:

<authentication-managed-identity resource="https://cognitiveservices.azure.com"
    output-token-variable-name="managed-id-access-token" ignore-error="false" />
<set-header name="Authorization" exists-action="override">
    <value>@("Bearer " + (string)context.Variables["managed-id-access-token"])</value>
</set-header>

Validate Azure AD Token

Validate JWT tokens from Microsoft Entra ID:

<validate-azure-ad-token tenant-id="{tenant-id}">
    <client-application-ids>
        <application-id>{client-app-id}</application-id>
    </client-application-ids>
</validate-azure-ad-token>

Conditional Logic (Choose)

Apply policies based on conditions:

<choose>
    <when condition='@(context.Request.Headers.GetValueOrDefault("X-Custom", "") == "value")'>
        <!-- policies when condition is true -->
    </when>
    <otherwise>
        <!-- fallback policies -->
    </otherwise>
</choose>

Return Custom Response

Return an immediate response without calling the backend:

<return-response>
    <set-status code="403" reason="Forbidden" />
    <set-header name="Content-Type" exists-action="override">
        <value>application/json</value>
    </set-header>
    <set-body>{"error": "Access denied"}</set-body>
</return-response>

Retry Logic

Retry failed requests with conditions:

<retry count="2" interval="1" first-fast-retry="true"
    condition='@(context.Response.StatusCode == 429 || context.Response.StatusCode &gt;= 500)'>
    <forward-request timeout="60" buffer-request-body="true" />
</retry>

For backend-pool failover, bound the retry budget independently of pool size. Use count="2" as the default unless workload testing justifies another value. This permits two retries after the initial attempt, for three total attempts. Two retries may already be more than sufficient for interactive traffic: a circuit-breaking pool removes a failed member from selection, and three consecutive failures against backends that were eligible moments earlier are a strong signal to stop and return control to the caller. Use retry-attempt and latency telemetry to justify reducing the count to 1 or increasing it above 2.

When the value must be managed outside the policy, use a plain numeric Named Value as the complete attribute value, for example count="{{backend-retry-count}}", and set that Named Value to 2. Keep pool cardinality as a separate concern only when policy behavior genuinely needs to count pool members.

Policy Expressions

Policy expressions use C# syntax within @() for single statements or @{} for multi-statement blocks.

Quotes in XML Attributes

Use single quotes around XML attribute values that contain policy expressions. This allows C# string literals and dictionary keys inside the expression to use normal double quotes without XML entity encoding.

<set-variable name="callerId" value='@((string)context.Variables["callerId"])' />

Keep ordinary non-expression XML attributes double-quoted. If an expression itself requires a single-quoted character or string literal, encode that apostrophe as &apos; or use a double-quoted XML attribute and encode its inner double quotes as &quot;.

Expressions in element text also use normal double quotes:

<value>@((string)context.Variables["callerId"])</value>

XML Entity Encoding

Policy expressions are C# embedded in XML, so XML parsing occurs before APIM evaluates the expression. In XML attribute values and element text:

  • Encode & as &amp;. Logical AND must therefore be written as &amp;&amp;.
  • Encode < as &lt;. This is required for less-than comparisons and the opening angle bracket of generic type arguments.
  • Encode > as &gt; for paired angle brackets and comparisons. A literal > is generally valid XML, but encoding it keeps expressions consistent and avoids ambiguity.
<set-variable name="attempt" value='@(context.Variables.GetValueOrDefault&lt;int&gt;("attempt", 0))' />
<when condition='@(attempt &lt;= retryLimit &amp;&amp; statusCode &gt;= 500)'>
    <!-- policies -->
</when>

These entities are decoded before C# evaluation, so APIM receives GetValueOrDefault<int>, <=, &&, and >=. Code shown outside an XML context, such as a csharp fenced block, should use ordinary C# characters without XML entity encoding.

Common Expressions

// Get header value
@(context.Request.Headers.GetValueOrDefault("header-name", "default"))

// Get query parameter
@(context.Request.Url.Query.GetValueOrDefault("param-name", "default"))

// Get URL path parameter
@(context.Request.MatchedParameters.GetValueOrDefault("param-name", "default"))

// Get subscription ID
@(context.Subscription.Id)

// Get client IP
@(context.Request.IpAddress)

// Read JSON body property
@(context.Request.Body.As<JObject>(preserveContent: true)["property"]?.ToString())

// Check header existence
@(context.Request.Headers.ContainsKey("header-name"))

// Get context variable
@(context.Variables.GetValueOrDefault<string>("var-name", "default"))

Multi-Statement Expression

<set-variable name="result" value='@{
    string[] value;
    if (context.Request.Headers.TryGetValue("Authorization", out value))
    {
        if(value != null &amp;&amp; value.Length &gt; 0)
        {
            return Encoding.UTF8.GetString(Convert.FromBase64String(value[0]));
        }
    }
    return null;
}' />

Allowed .NET Types and Members (CRITICAL)

Policy expressions may only reference the .NET Framework types and members on APIM's allow-list. Anything outside the list causes a deploy-time ValidationError: One or more fields contain incorrect values with no further detail, which is hard to diagnose.

Before using a type or member in a policy expression, verify it appears on the official allow-list. Common pitfalls:

  • Whole namespaces are absent. System.Globalization (e.g. DateTimeStyles, CultureInfo), System.Threading, System.IO (except StringReader/StringWriter), System.Net.Http, System.Reflection, and System.Diagnostics are not allowed.
  • System.DateTime is restricted. Allowed members include Parse, UtcNow, Now, AddSeconds, Subtract, ToString, Ticks. Not allowed: TryParse, TryParseExact, ParseExact, ToUniversalTime, ToLocalTime, SpecifyKind. For round-trip-safe time math, prefer System.DateTimeOffset (All members allowed) and ToUnixTimeSeconds() / ToUnixTimeMilliseconds().
  • System.Enum is restricted to Parse, TryParse, ToString. No GetValues, GetNames, IsDefined.
  • System.Text.RegularExpressions.Regex is restricted to the constructor plus IsMatch, Match, Matches, Replace, Unescape, Split. No CompileToAssembly, CacheSize.
  • Numeric primitives are fully allowed, so int.TryParse, long.TryParse, double.TryParse are safe.
  • JSON via Newtonsoft.Json is the only supported JSON library; do not use System.Text.Json.

When a member you need is not allowed, refactor to an equivalent that is. Examples:

DisallowedAllowed replacement
DateTime.TryParse(s, ..., DateTimeStyles.RoundtripKind, out dt)Store as Unix epoch via DateTimeOffset.UtcNow.ToUnixTimeSeconds(), parse with long.TryParse
DateTime.ParseExact(s, fmt, CultureInfo.InvariantCulture)DateTime.Parse(s) (allowed) or epoch-based representation
Enum.GetValues(typeof(T))Hard-code the comparison values or store as a string
System.Text.Json.JsonSerializer.Deserialize<T>(s)JsonConvert.DeserializeObject<T>(s)

If you are unsure whether a member is allowed, fetch the allowed types table and confirm before writing the expression.

Reference Documentation

  • Sample-owned policies in this repo: samples/<sample-name>/apim-policies/ (all XML policies specific to one sample)
  • Shared policies in this repo: shared/apim-policies/ (reusable policy XML fragments)

Policy-loading helpers must check the sample's apim-policies/ directory first. During migration only, they may check the sample root second so existing root-level policies continue to work. New policy files must not use the fallback location.

Official Documentation

Download now (ZIP)

  • Other versions1 project uses a version of this.

    Word for word · 0

    None found.

    Edited · 1

    1. Azure-Samples/AI-Gateway★ 98859% same

    Wrote their own version · 0

    None found.

    How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.

  • In this repository4 other files it gives its agents.

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.