This chapter covers Azure API Management policies and transformation, a critical topic for the AZ-204 exam under Objective 5.1: 'Integrate with API Management.' API Management policies allow you to modify the behavior of your APIs without changing the actual API code. Expect roughly 10-15% of exam questions to touch on API Management, with a significant portion focusing on policy configuration, expression syntax, and common transformation scenarios. Mastering policies is essential for securing, throttling, and transforming APIs in enterprise scenarios.
Jump to a section
Imagine a package sorting facility for an international shipping company. Every package entering or leaving the facility must pass through a customs inspection station. This station is not just a single checkpoint; it is a configurable pipeline of inspection steps. Each step can be a stamp, a scan, a weight check, or a form addition. For example, a 'rate limiting' step is like a conveyor belt that only allows 100 packages per minute — if more arrive, they are held in a queue or rejected. A 'transform XML to JSON' step is like a translator who rewrites the shipping manifest from French to English. A 'validate JWT' step is like a document checker who verifies the passport and visa before the package can proceed. The entire pipeline is defined by a set of policies that are applied in order. The facility manager (API Management instance) can add, remove, or reorder these steps without redesigning the entire facility. Each policy has a scope: it can apply globally (to all packages), to a specific product (e.g., express shipping), to a specific API (e.g., electronics), or to a single operation (e.g., track a package). The policies are executed in a fixed sequence: first incoming request policies (inbound), then backend request policies (if any), then backend response policies, and finally outgoing response policies (outbound). This is exactly how Azure API Management policies work — they are a configurable pipeline of XML-based rules that execute in four sections: inbound, backend, outbound, and on-error. Each section can contain multiple policy statements that inspect, transform, rate-limit, or validate API requests and responses as they flow through the gateway.
What Are API Management Policies?
Azure API Management (APIM) policies are a powerful feature that allows you to change the behavior of your API through configuration rather than code. Policies are a collection of statements that are executed sequentially on the request or response of an API. They are defined in XML format and can be applied at different scopes: global (All APIs), product, API, or operation. The most specific scope wins, and if multiple policies apply, they are combined using a merge strategy.
Why Policies Exist
Policies exist to address cross-cutting concerns that are common across many APIs: authentication, rate limiting, caching, logging, transformation, and validation. Without policies, you would need to implement these features in every backend service, leading to duplication and inconsistency. With APIM policies, you centralize these concerns in the gateway, making it easier to enforce security and governance.
How Policies Work Internally
When a request arrives at the APIM gateway, it passes through four policy sections in order:
Inbound: Policies applied to the incoming request before it is forwarded to the backend. This is where you typically handle authentication, rate limiting, URL rewriting, and request transformation.
Backend: Policies applied to the request that is sent to the backend (if you need to modify the backend request after inbound processing). This is less commonly used.
Outbound: Policies applied to the response from the backend before it is sent to the client. This is where you transform response bodies, add headers, or cache responses.
On-error: Policies applied when an error occurs during processing. This allows you to customize error responses.
Each section is a container of policy statements. The order of statements within a section matters. For example, if you have a rate-limit policy and a set-header policy in the inbound section, the rate limit is checked first, then the header is added.
Policy Expressions
Policies can include expressions written in C# using a subset of .NET Framework types. Expressions are enclosed in @(expression) in the XML. They are evaluated at runtime. For example:
<set-header name="Custom-Header" exists-action="override">
<value>@(context.Request.Headers.GetValueOrDefault("X-Forwarded-For", ""))</value>
</set-header>Key objects available in expressions:
- context: Provides access to the request, response, API, product, user, and subscription.
- context.Request: Contains headers, query parameters, body, URL, method.
- context.Response: Contains headers, body, status code.
- context.Api: Contains API name, path, service URL.
- context.Subscription: Contains subscription key, name, etc.
Common Policy Statements
Rate Limiting:
<rate-limit calls="10" renewal-period="60" />This limits the number of calls per subscription key to 10 per 60 seconds. If exceeded, returns 429 Too Many Requests.
Quota:
<quota calls="1000" renewal-period="86400" />Limits total calls per subscription over a 24-hour period.
Validate JWT:
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized">
<openid-config url="https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration" />
<required-claims>
<claim name="aud" match="all">
<value>api://{client-id}</value>
</claim>
</required-claims>
</validate-jwt>Set Header:
<set-header name="X-Request-ID" exists-action="override">
<value>@(Guid.NewGuid().ToString())</value>
</set-header>Set Body:
<set-body template="liquid">
{
"name": "{{body.name}}",
"email": "{{body.email}}"
}
</set-body>Supports Liquid templates or inline expressions.
Rewrite URL:
<rewrite-uri template="/api/v2/orders/{orderId}" />This rewrites the incoming URL before forwarding to the backend.
CORS:
<cors allow-credentials="true">
<allowed-origins>
<origin>https://myapp.com</origin>
</allowed-origins>
<allowed-methods>
<method>GET</method>
<method>POST</method>
</allowed-methods>
<allowed-headers>
<header>*</header>
</allowed-headers>
</cors>Policy Scopes and Hierarchy
Policies can be defined at four scopes:
Global: Applied to all APIs within the APIM instance. Defined in the All APIs section.
Product: Applied to all APIs associated with a specific product.
API: Applied to all operations of a specific API.
Operation: Applied to a specific operation (e.g., GET /orders/{id}).
When multiple policies apply, they are combined. The order of evaluation is: Global -> Product -> API -> Operation. For each section (inbound, backend, outbound, on-error), the policies are merged. If two policies set the same header, the more specific scope overrides the less specific one, unless exists-action is set to override or append.
Policy Fragments
Policy fragments are reusable policy snippets that can be included in multiple policies. They help reduce duplication. For example, you can create a fragment for JWT validation and include it in multiple APIs:
<include fragment="jwt-validate" />Caching Policies
cache-lookup: Checks the cache for a response before forwarding to the backend.
cache-store: Stores the response in the cache after receiving it from the backend.
cache-lookup-value and cache-store-value: For caching arbitrary values by key.
Example:
<inbound>
<cache-lookup vary-by-developer="false" vary-by-developer-groups="false" downstream-caching-type="none">
<vary-by-query-parameter>query</vary-by-query-parameter>
</cache-lookup>
</inbound>
<outbound>
<cache-store duration="3600" />
</outbound>Error Handling
On-error policies are triggered when an error occurs. You can use set-variable to capture error details and return-response to return a custom error response.
<on-error>
<set-variable name="errorMessage" value="@(context.LastError.Message)" />
<return-response>
<set-status code="500" reason="Internal Server Error" />
<set-body>{"error": "@(context.Variables.GetValueOrDefault<string>("errorMessage"))"}</set-body>
</return-response>
</on-error>Transformation Use Cases
XML to JSON Conversion: Use set-body with a Liquid template or an inline expression to convert the response body format.
Field Filtering: Remove sensitive fields from the response using JSONPath or XPath.
Header Enrichment: Add correlation IDs, user information, or API version.
URL Rewriting: Map external URLs to internal backend URLs.
Protocol Translation: Convert between SOAP and REST using policies.
Performance Considerations
Policies are executed synchronously. Complex expressions or large body transformations can increase latency.
Use caching to reduce backend calls.
Avoid heavy computation in expressions; consider moving logic to the backend.
Policy fragments reduce policy size but do not affect runtime performance.
Interaction with Other Technologies
Azure AD: Policies can validate JWT tokens issued by Azure AD.
Azure Key Vault: Policies can reference secrets from Key Vault for authentication.
Azure Functions: Policies can call Azure Functions for custom validation or transformation using the send-request policy.
Application Insights: Policies can integrate with Application Insights for logging and monitoring.
Configuration and Verification
Policies are defined in the Azure portal under each scope (All APIs, Products, APIs, Operations). You can also manage them programmatically via Azure CLI, PowerShell, or ARM templates. To verify policies, use the Test Console in the portal to send requests and inspect the processed request/response.
Identify the Scope
Determine where the policy should be applied: globally (All APIs), to a product, to a specific API, or to a single operation. The scope affects how the policy is inherited and overridden. For example, a global rate limit applies to all APIs, while an operation-level policy only applies to that specific endpoint. Understanding scope hierarchy is crucial because policies at more specific scopes override less specific ones. Use the Azure portal to navigate to the desired scope and open the Policy editor.
Add Inbound Policies
Inbound policies are processed before the request is sent to the backend. Common inbound policies include authentication (validate-jwt), rate limiting (rate-limit), IP filtering (ip-filter), and request transformation (set-header, set-body, rewrite-uri). Add policy statements in the `<inbound>` section. The order matters: for example, you typically want to validate authentication before applying rate limiting. Use expressions for dynamic values. Save the policy after editing.
Add Backend Policies
Backend policies are applied to the request that is sent to the backend service. This section is optional and rarely used. It allows you to modify the backend request after all inbound processing. For example, you might add a header that indicates the original client IP. Place statements inside `<backend>` section. If not needed, leave it empty.
Add Outbound Policies
Outbound policies are applied to the response from the backend before it is returned to the client. Common outbound policies include response transformation (set-body, set-header), caching (cache-store), and CORS (cors). For example, you can convert XML to JSON using set-body with a Liquid template. Add statements inside `<outbound>` section. Ensure you handle errors by checking status codes.
Add On-Error Policies
On-error policies are triggered when an error occurs during policy execution or when the backend returns an error status code. Use this section to customize error responses, log errors, or return a fallback response. For example, you can set a custom error message and status code using return-response. Place statements inside `<on-error>` section. Always include an on-error policy to provide consistent error handling.
Test and Validate
Use the Azure portal's Test tab to send requests to the API and inspect the processed request and response. Check that headers are set correctly, bodies are transformed, and rate limits are enforced. Use the Trace feature to see the exact policy execution order and any errors. Verify that the policy behaves as expected across different scopes. Also test edge cases like missing tokens or exceeded quotas.
Scenario 1: Enterprise API Gateway with Rate Limiting and Authentication
A large e-commerce company exposes its product catalog and order APIs to third-party developers. The company uses Azure API Management as the gateway. They need to enforce authentication using Azure AD and rate limit each developer subscription to 1000 calls per hour. Additionally, they want to add a correlation ID to every request for tracing. The solution: at the global scope, they add a validate-jwt policy that checks tokens against Azure AD. At the product scope, they add a rate-limit policy with calls="1000" and renewal-period="3600". They also add a set-header policy in the inbound section to add a correlation ID using @(Guid.NewGuid().ToString()). In production, they monitor usage and adjust rate limits based on tier. A common mistake is placing the rate-limit policy before authentication, which can lead to unauthenticated requests consuming quota. The correct order is validate-jwt first, then rate-limit.
Scenario 2: Legacy SOAP to REST Transformation
A financial institution has a legacy SOAP backend that they want to expose as a RESTful API to mobile clients. They use APIM to transform requests and responses. In the inbound section, they use set-body to convert the incoming JSON request to a SOAP envelope using a Liquid template. They also set the Content-Type header to text/xml. In the outbound section, they use set-body to parse the SOAP response and convert it to JSON. They also handle errors by checking the SOAP fault and returning a standard HTTP error. Performance is critical: body transformations can be CPU-intensive, so they use caching to reduce repeated transformations. They also use the send-request policy to call a transformation service if needed. Misconfiguration often leads to XML parsing errors or missing namespaces. They test extensively with different payloads.
Scenario 3: Multi-Tenant API with Tenant-Specific Policies
A SaaS provider offers a multi-tenant API where each tenant has custom requirements. They use APIM with policies at the API scope. For each tenant, they create a separate product and assign policies to that product. For example, Tenant A needs IP filtering, while Tenant B needs a custom header. They use policy expressions to read tenant ID from the subscription context and apply conditional logic. They also use policy fragments to share common policies like logging. In production, they have hundreds of tenants, and policy management becomes complex. They use ARM templates to automate policy deployment. A common issue is policy size limit: the total policy XML for an API cannot exceed 64 KB. They break large policies into fragments. They also use the choose policy for conditional branching.
What AZ-204 Tests on API Management Policies (Objective 5.1)
The exam focuses on your ability to configure policies to secure, transform, and manage APIs. Specifically, you should know:
How to apply policies at different scopes (global, product, API, operation) and the inheritance/override behavior.
The syntax and usage of common policy statements: rate-limit, quota, validate-jwt, set-header, set-body, rewrite-uri, cors, cache-lookup, cache-store.
How to use policy expressions with C# syntax and the context object.
How to handle errors using the on-error section and return-response.
How to use policy fragments to reuse policy code.
Common Wrong Answers and Why Candidates Choose Them
Wrong: 'Policies can only be applied at the API scope.' Candidates confuse scope hierarchy. Actually, policies can be applied at global, product, API, and operation scopes. The most specific scope wins.
Wrong: 'The rate-limit policy limits calls per IP address.' The default key is subscription key, not IP. Candidates overlook the calls and renewal-period attributes and the key type.
Wrong: 'Policy expressions use JavaScript.' They use C#. Candidates familiar with other platforms might assume JavaScript. The correct syntax is @(expression).
Wrong: 'The outbound section executes before the backend response is received.' Actually, outbound executes after the backend response. The order is inbound, backend, outbound.
Specific Numbers and Terms That Appear on the Exam
The default renewal-period for rate-limit is 60 seconds.
The quota policy uses calls and renewal-period in seconds (e.g., 86400 for 24 hours).
The cache-store duration is in seconds.
The validate-jwt policy can use OpenID Connect discovery URL.
The set-body policy supports Liquid templates and inline expressions.
The maximum policy XML size is 64 KB.
The choose policy allows conditional logic with when conditions.
Edge Cases and Exceptions
If a policy in a more specific scope sets a header with exists-action="override", it overrides a less specific scope's header. If exists-action="append", it adds multiple header values.
The cors policy must be applied at the API or global scope; it cannot be applied at the operation scope.
The validate-jwt policy can be used without OpenID configuration by specifying the signing key directly.
The cache-lookup policy must be in the inbound section, and cache-store in the outbound section.
How to Eliminate Wrong Answers
If a question asks about changing the response body format, look for set-body with a template.
If a question involves limiting calls per subscription, look for rate-limit or quota.
If a question involves JWT validation, look for validate-jwt.
If a question involves CORS, look for the cors policy.
If a question involves error handling, look for on-error section.
Eliminate any answer that mentions JavaScript, IP-based rate limiting without specifying key, or incorrect scope application.
Policies are XML-based and executed in order: inbound, backend, outbound, on-error.
Policy scopes: global, product, API, operation. More specific overrides less specific.
Use `rate-limit` for short-term throttling and `quota` for long-term limits.
Policy expressions use C# syntax with `@(expression)` and the `context` object.
The `validate-jwt` policy can validate tokens against Azure AD or custom issuers.
Use `set-body` with Liquid templates or inline expressions for body transformation.
On-error section allows custom error responses using `return-response`.
Policy fragments reduce duplication but do not affect runtime performance.
Maximum policy XML size is 64 KB per scope.
CORS policy cannot be applied at operation scope; it must be at API or global scope.
These come up on the exam all the time. Here's how to tell them apart.
rate-limit policy
Limits calls over a short sliding window (e.g., 10 calls per 60 seconds).
Returns 429 Too Many Requests when exceeded.
Resets after the renewal period.
Can be keyed by subscription, IP, or custom key.
Suitable for preventing bursts.
quota policy
Limits total calls over a longer fixed period (e.g., 1000 calls per 24 hours).
Returns 429 Too Many Requests when exceeded.
Resets at the end of the renewal period.
Always keyed by subscription.
Suitable for enforcing long-term usage limits.
Mistake
Policies can only be applied at the API level.
Correct
Policies can be applied at four scopes: global (All APIs), product, API, and operation. The most specific scope overrides less specific ones.
Mistake
Rate limiting is always based on the client IP address.
Correct
By default, the `rate-limit` policy uses the subscription key as the counter key. You can change the key to IP or other values using the `key` attribute.
Mistake
Policy expressions are written in JavaScript.
Correct
Policy expressions use a subset of C# and are enclosed in `@(expression)`. They have access to the `context` object and .NET types.
Mistake
The outbound section runs before the backend request is sent.
Correct
The execution order is: inbound, backend, outbound, on-error. Outbound runs after the backend response is received.
Mistake
Policy fragments are evaluated at design time and cannot contain expressions.
Correct
Policy fragments can contain expressions and are evaluated at runtime. They are simply reusable snippets.
Reveal each answer, then mark whether you got it right. Score 60%+ to unlock the next chapter.
The `rate-limit` policy limits calls over a short sliding window (e.g., 10 calls per 60 seconds) and is used to prevent bursts. The `quota` policy limits total calls over a longer fixed period (e.g., 1000 calls per 24 hours) and is used for long-term usage limits. Both return 429 Too Many Requests when exceeded. `rate-limit` can be keyed by subscription, IP, or custom key, while `quota` is always keyed by subscription.
No, policy expressions use a subset of C#. They are enclosed in `@(expression)` and have access to the `context` object and .NET types like `Guid`, `DateTime`, and `String`. You cannot use JavaScript. If you need custom logic, consider using the `send-request` policy to call an Azure Function.
Navigate to the specific operation (e.g., GET /orders/{id}) in the Azure portal and open the Policy editor. Add your policy statements in the appropriate section (inbound, outbound, etc.). This operation-level policy will override any less specific policies (API, product, global) for that operation.
The most specific scope's policy wins. If two policies at the same scope set the same header, the one that appears later in the XML order wins. You can control this behavior using the `exists-action` attribute: `override` (default), `append`, `delete`, or `skip`.
Use the `set-body` policy in the outbound section with a Liquid template. For example: `<set-body template="liquid">{ "data": {{body | json}} }</set-body>`. Alternatively, you can use an inline expression with `context.Response.Body.As<XElement>()` and convert it.
The total XML size of all policies at a given scope (including fragments) cannot exceed 64 KB. If your policy is larger, consider using policy fragments or reducing inline expressions.
Yes, use `cache-lookup` in the inbound section to check the cache, and `cache-store` in the outbound section to store the response. You can configure caching duration and vary-by parameters.
You've just covered API Management Policies and Transformation — now see how well it sticks with free AZ-204 practice questions. Full explanations included, no account needed.
Done with this chapter?