Loom APIM Policy Editor — Fabric-parity spec¶
Captured 2026-05-26 by catalog agent. "APIM Policy" = the XML policy document attached to one of four scope levels in Azure API Management — Global (service), Product, API, or API Operation. Policies are the request/response pipeline: auth (validate-jwt, validate-azure-ad-token), throttling (rate-limit, quota), transforms (set-header, set-body, rewrite-uri, json-to-xml, xml-to-json), routing (set-backend-service, forward-request), caching, and observability.
Overview¶
Every API call routed through APIM executes a policy document at four ordered scopes — Global → Product → API → Operation — with each child scope inheriting from its parent through the <base /> placeholder. Each scope's XML has four zones: <inbound> (before backend), <backend> (forward-request control), <outbound> (after backend), <on-error> (catch). The portal editor is a Monaco XML pane with a snippet picker on the right (Add policy → categorized list of all built-in policies with their parameter forms), policy expression intellisense (@(context.User.Id)-style), well-formedness validation on Save, and an integrated test runner that executes the policy against a simulated request and shows trace output.
UI components (Azure portal)¶
Scope navigation¶
- Policy editing is reached from four entry points, all rendering the same editor with different
scopeand target ids: - Global: APIM service → APIs → All APIs → Policies tab → "
</>Policy code editor" - Product: Products → {product} → Policies tab
- API: APIs → {api} → Design → Inbound/Backend/Outbound/On-error
</>buttons - Operation: APIs → {api} → Design → {operation} → Inbound/Backend/Outbound/On-error
</>buttons
Editor chrome¶
- Tabs: Code view · Form view (limited — only some policies have a form representation)
- Header buttons: Save · Discard · Calculate effective policy (shows the merged XML after
<base />resolution from all parent scopes) - Read-only mini-pane on top showing scope path (e.g., "API: orders-api → Operation: getOrderById")
Monaco XML editor¶
- Syntax highlighting + bracket matching for XML
- Intellisense over the APIM policy schema (element names, attribute names, allowed children) — triggered by
<and Ctrl/Cmd+Space - Intellisense over
@(...)policy expressions — C# subset withcontext.*,context.Request.*,context.Response.*,context.User.*,context.Variables[...],context.Product.*,context.Api.*,context.Operation.* - Hover docs per element/attribute (pulled from the policy reference docs)
- Squiggle markers for invalid XML, unknown elements, and missing required attributes
- Ctrl+/ to toggle XML comments
- Auto-indent + format on save
Snippet picker (right rail)¶
- Categorized list of every built-in policy:
- Access restriction: rate-limit, rate-limit-by-key, quota, quota-by-key, ip-filter, validate-jwt, validate-azure-ad-token, validate-client-certificate, check-header
- Authentication: authentication-basic, authentication-certificate, authentication-managed-identity
- Caching: cache-lookup, cache-store, cache-remove-value, cache-lookup-value, cache-store-value
- Cross-domain: cors, jsonp, cross-domain
- Dapr / GraphQL / SOAP sub-groups
- Routing: set-backend-service, forward-request, return-response, redirect-content-urls, rewrite-uri
- Transformation: set-header, set-query-parameter, set-body, set-variable, set-method, set-status, json-to-xml, xml-to-json, find-and-replace
- Validation: validate-content, validate-parameters, validate-headers, validate-status-code
- Observability: trace, emit-metric, log-to-eventhub, mock-response
- Each snippet click → inserts XML stub at the caret with placeholders
- Per-snippet "Insert form…" dialog where supported (e.g., validate-jwt form with openid-config URL, audience, issuer, required claims)
Test runner (calculate + test)¶
- "Calculate effective policy" merges all parent scopes through
<base />and shows the final document - "Test" panel (only in Design tab integration for API/operation scopes) executes the policy against a mock or real request and renders the trace output (per-policy timing + variable state)
Save behavior¶
- Server-side validation: APIM returns 4xx with the offending element/line if XML is malformed or references an unknown policy element
- Side-effect: policies are versioned at the scope level (no separate revision); changes are live immediately
What Loom has¶
apps/fiab-console/lib/editors/apim-editors.tsxlines 422-555:ApimPolicyEditor- Live ARM-REST wired via
lib/azure/apim-client.tsto: - Global scope:
Microsoft.ApiManagement/service/{name}/policies/policy - API scope:
Microsoft.ApiManagement/service/{name}/apis/{aid}/policies/policy - Product scope:
Microsoft.ApiManagement/service/{name}/products/{pid}/policies/policy - BFF route:
GET/PUT /api/items/apim-policy/[id]?scope=service|api|product&apiId=...&productId=... - Form fields: scope dropdown (service / api / product), conditional apiId or productId Input, value (XML)
- Default
<policies>template seeded with commented-out validate-jwt + active rate-limit calls=120/60s - Client-side
DOMParserwell-formed-XML validation before Save (isWellFormedXmlguard) - Monaco XML editor (
MonacoTextareawithlanguage="xml", self-hosted Monaco assets perscripts/copy-monaco-assets.mjs) — bracket matching, syntax highlighting, Ctrl+S save, dirty-flag tracking - Ribbon: Save · Reload · Validate XML (
isWellFormedXmlinvoked client-side; SSR-safe fallback unit-tested at__tests__/apim-xml-validation.test.ts) · Global / API / Product / Operation scope buttons all wired with real handlers - Operation scope (
apis/{aid}/operations/{oid}/policies/policy) wired for read+upsert viaapim-client.ts(v3.27) - Grade: B — real ARM CRUD across all four scopes (Global / API / Product / Operation), Monaco XML editor, client-side well-formed-XML guard before Save, sensible default policy template, accurate ribbon wiring, dirty-flag prevents stale-keystroke clobber on async PUT (Phase 4.5). Lifted from C by v3.27 (Operation scope) + v3.28 (Monaco). Remaining gaps (snippet picker, expression IntelliSense, effective-policy calculator, test runner) are tracked below and don't block production use.
Gaps for parity¶
- Operation scope —
apis/{aid}/operations/{oid}/policies/policyunwired; operation-scope policies are critical for endpoint-specific auth/throttling and currently unreachable - Monaco XML editor replaces
<textarea>— needs syntax highlighting, bracket matching, auto-indent - APIM policy XML intellisense — element/attribute autocomplete from the policy schema (the OpenAPI schema for APIM policies is published; can be ingested into Monaco LSP)
- Policy expression intellisense —
@(...)C# expressions withcontext.*IntelliSense - Snippet picker right rail — categorized list of all built-in policies with insert-as-XML
- Form-view per snippet — at minimum for the high-traffic policies (validate-jwt, rate-limit, set-header, cors, mock-response)
- Calculate effective policy — recursive
<base />resolution from parent scopes; needs Loom to fetch all four scopes server-side and merge - Test runner — execute the policy against a simulated request, render trace per policy
- Server-side validation surfacing — Loom currently surfaces the APIM error message but doesn't map it to a line/column in the editor; Monaco markers would fix this
- Scope path breadcrumb — read-only header showing where you are (helps when navigating from an API/operation context)
- Policy reference docs hover — link out to learn.microsoft.com policy reference per element on hover
- Default templates per scope — Global vs Product vs API vs Operation each have different sensible defaults; today all four share one template
<base />lint — Azure built-in policy requires every zone include<base />to inherit; surface as a warning when missing
Backend mapping¶
- Primary backend = Azure APIM ARM REST (already wired for service / API / Product):
- Global:
PUT /service/{svc}/policies/policy?api-version=2024-06-01-previewbody{ properties: { value: <xml>, format: 'xml' } } - API:
PUT /service/{svc}/apis/{apiId}/policies/policy - Operation:
PUT /service/{svc}/apis/{apiId}/operations/{operationId}/policies/policy(UNWIRED — add tolib/azure/apim-client.tsgetPolicy/upsertPolicywith a fourth scope kind) - Product:
PUT /service/{svc}/products/{productId}/policies/policy - Format options:
xml(default) orrawxml(treats&as literal, not escape) orxml-link/rawxml-link(fetch from URL) - Effective policy calc: no server-side ARM endpoint — Loom must
GETall parent-scope policies and string-substitute<base />in each zone (inbound/backend/outbound/on-error) walking up: operation → api → product (any product the op's API belongs to — choose one) → global - Schema for Monaco intellisense: APIM publishes the policy XSD at
https://management.azure.com/...(or use the public docs JSON). Cache locally inlib/azure/apim-policy-schema.jsonand feed into Monaco via themonaco-yaml-style XML language service - Test runner: simplest path is to issue a real request to the gateway with
Ocp-Apim-Trace: trueand follow thetrace-locationheader; alternative is APIM's preview/policies/policy/$validateAndExecuteoperation (not GA yet)
Required Azure resources¶
- Azure APIM instance (already provisioned)
- UAMI with "API Management Service Contributor" (already granted)
- Optional: an Application Insights workspace if Loom surfaces the trace output natively — APIM already pipes traces there via the diagnostic setting
- No new bicep — policy XML is runtime data managed through ARM
Estimated effort¶
3 sessions for B+ parity: - Session 1: Add Operation scope to apim-client + BFF route; add scope=operation to the editor with API + Operation pickers (2 h) - Session 2: Swap <textarea> for @monaco-editor/react with XML language + bracket pair colorization + format-on-save (2 h) - Session 3: Snippet picker right rail with the top-20 built-in policies + form-view dialogs for validate-jwt / rate-limit / cors / set-header (4 h)
A+ parity (full intellisense from the policy XSD, @(...) expression intellisense, effective-policy calculator, test runner with trace) adds ~3 more sessions; this is the right time to invest because the policy editor is the highest-value surface in the APIM trio for governance / FedCiv compliance use cases.