Azure Governance and Identity for Integration Architects

In the Azure PaaS map post, governance and identity got a paragraph and a firm line: managed identity everywhere, secrets in Key Vault, posture visible. That paragraph is the one most PaaS write-ups skip; they cover compute, messaging, and data, then wave at security on the way out.

For an integration platform in a regulated industry, though, this layer isn’t the afterthought. It’s the part that decides whether the thing can run at all. So this post gives it the space the map couldn’t. Azure governance and identity are where a proof of concept becomes something an auditor will sign off on, and where many otherwise good architectures quietly fail their first compliance review.

The two jobs this layer does

Governance and identity sounds like one concern. In practice it’s two, and keeping them apart makes the design clearer.

Diagram splitting the layer into two columns. Identity answers "who" and contains Microsoft Entra ID, managed identities, and RBAC role assignments. Governance answers "whether and how" and contains Azure Policy, Defender for Cloud, and the audit trail. A note reads that both are always required.
Identity answers “who”: Entra ID, managed identities, RBAC. Governance answers “whether and how”: policy, D\defender, the audit trail. Identity without governance is secure but unprovable; governance without identity is documented but wide open. A platform needs both.

Identity answers who. Who is this caller? What are they allowed to reach? This is Entra ID, managed identities, and role assignments the machinery that authenticates and authorizes every hop in the platform.

Governance answers whether and how. Is this configuration allowed? Can we prove it stayed allowed? This is Azure Policy, Defender for Cloud, and the audit trail the machinery that constrains what the platform can be and evidences it after the fact.

An integration platform needs both. Identity without governance gives you a secure platform you can’t prove is secure. Governance without identity gives you a well-documented platform anyone can walk into. So let’s take each in turn, then the compliance frame that ties them together.

Identity: managed identity as the default

Here’s the single most important rule in this layer. Every service authenticates as itself, with no secret in configuration. That’s what managed identity gives you, and it’s the foundation everything else sits on.

Without it, you’re back to connection strings and API keys scattered across app settings and config files. Each one is a secret that can leak, expire, or get committed to a repo by accident. With managed identity, the platform issues each service an identity in Entra ID, and that identity authenticates directly to Key Vault, SQL, Service Bus, and Storage. No secret changes hands. Nothing to rotate, nothing to leak.

A few design points that matter for integration specifically:

  • System-assigned or user-assigned? A system-assigned identity is tied to one resource and dies with it. A user-assigned identity is a standalone resource you attach to many. For an integration platform with several services that need the same access, a user-assigned identity is usually cleaner: you grant permissions once and attach it everywhere, rather than managing a dozen separate grants.
  • Least privilege, per service. Managed identity makes authentication clean, but authorisation is still on you. Each service should hold exactly the roles it needs and nothing more. The integration service that reads from Service Bus doesn’t need write access to the whole storage account. So scope the role assignments tightly, because a broad grant is a standing risk long after anyone remembers making it.
  • RBAC over access policies. Where a service supports Azure RBAC for its data plane, Key Vault now prefers it over the older per-resource access policy model. RBAC gives you one consistent permission model across the platform, which is far easier to audit than a patchwork of resource-specific policies.

Identity: Entra ID and the human side

Managed identity handles service-to-service. Entra ID also governs the human and external edges of the platform.

For inbound calls, Entra ID handles authentication and authorisation validating tokens, enforcing scopes, and applying conditional access where the risk warrants it. App Service and API Management both integrate with it directly, so the platform can offload the whole OIDC flow rather than hand-rolling token validation. That’s less code and, more to the point, less code to get wrong.

The honest note: Entra ID’s built-in flows are excellent for standard cases and awkward for unusual ones. If your authorization logic is genuinely complex, with per-tenant rules, dynamic scopes, and fine-grained resource permissions, understand where the platform’s built-in handling stops and your own logic has to begin. Leaning on Easy Auth for something it wasn’t designed for is a common way to end up with authorization gaps you don’t discover until an audit.

Secrets: Key Vault for what can’t be an identity

Managed identity removes most secrets. It doesn’t remove all of them. Third-party API keys, certificates, signing keys these can’t be a managed identity, so they need somewhere safe to live. That’s Key Vault.

Flow diagram. An integration service authenticates as its user-assigned managed identity in Entra ID. That identity reaches SQL, Service Bus, and Storage directly with no secret changing hands. For third-party API keys and certificates that can't be a managed identity, the service reads them from Key Vault at runtime. A note explains that rotating a secret becomes a Key Vault operation rather than a redeploy, and every read is logged.
A service authenticates as its Entra ID-managed identity, reaching SQL, Service Bus, and Storage with no secrets changing hands. For the few secrets that can’t be an identity-based third-party key or certificate, it reads them from Key Vault at runtime using the same identity.

The pattern is straightforward. Secrets live in Key Vault, and services read them at runtime using their managed identity. No secret sits in configuration; the app holds a reference, and the platform resolves it. As a result, rotating a secret is a Key Vault operation, not a redeploy.

For a regulated integration platform, Key Vault also stores the certificates that underpin private connectivity and signing, and it provides an access log of every secret read, which matters more than it sounds, because “who accessed this key and when” is a question auditors actually ask.

Governance: policy, posture, and the audit trail

Identity secures the platform. Governance proves it and constrains it so it can’t drift out of compliance.

Azure Policy enforces the guardrails. Policy lets you assert rules across the platform and block or flag anything that violates them. No public endpoints. Encryption required. Approved regions only, which matters directly under data-residency rules. Policy is what stops a well-meaning change from quietly breaking a compliance requirement, because it refuses the change rather than trusting everyone to remember the rule.

Defender for Cloud gives you posture. It surfaces misconfigurations, missing controls, and active threats across the platform, and scores you against benchmarks. For an integration platform touching regulated data, that continuous posture view is the difference between finding a gap yourself and having an auditor find it for you.

The audit trail evidences everything. Azure Monitor and the activity log record what changed, who changed it, and when. Under most compliance regimes, you don’t just have to be secure; you have to prove you were secure, continuously, over time. The audit trail is that proof. So wire it in from the start, because you can’t reconstruct an audit trail you didn’t capture.

The compliance frame: why this layer is non-negotiable

Everything above applies to any serious platform. In a regulated Dutch healthcare context, though, the frame is sharper, and it’s worth naming the constraints that turn best practices into requirements.

Under the AVG, the Dutch implementation of the GDPR imposes strict obligations on the handling, minimization, and residency of personal data. Approved-region policy, tight role scoping, and the access trail stop being good hygiene and become the evidence you’re meeting those obligations. NEN 7510, the Dutch standard for information security in healthcare, adds specific controls around access management and traceability that map almost directly onto Entra ID role assignments and the audit trail. DORA brings operational-resilience and third-party-risk requirements that lean on the same posture and logging foundations. And the EU AI Act, where the platform touches AI workloads, layers on transparency and oversight duties that again rest on identity and audit.

The through-line: these regimes don’t ask for exotic new machinery. They ask you to apply identity, policy, and audit rigorously, and to prove it. So the governance layer isn’t compliance overhead bolted onto the architecture. Done right, it is the compliance posture, expressed as configuration.

Where this layer gets over-applied

Consistent with the series the honesty section. Rigour in this layer is right; ceremony for its own sake is not.

  • Not every secret needs a Key Vault reference if it isn’t a secret. A non-sensitive configuration value doesn’t belong in Key Vault just because everything else does. Reserve it for things that actually need protecting, or you bury the real secrets in noise.
  • Not every workload needs the strictest tier of every control. A platform handling public reference data doesn’t need the same isolation and scrutiny as one touching patient records. Match the rigour to the data classification, rather than applying maximum controls uniformly and paying for it in friction everywhere.
  • Policy that only flags is policy that gets ignored. A pile of advisory policies nobody acts on is worse than a few enforced ones, because it creates the appearance of governance without the substance. Enforce what matters; don’t drown the signal.

The shape of it

For an integration architect, Azure governance and identity are the layer that decides whether the platform is allowed to exist, not just whether it works. Managed identity removes the secrets. Entra ID governs who gets in. Key Vault holds what’s left. Policy constrains the platform, Defender watches it, and the audit trail proves it continuously, the way every regime from AVG to the EU AI Act actually demands. Get this layer right, and compliance stops being a gate you dread. It becomes a property the architecture already has.

Want the layer this sits inside? The Azure PaaS map puts governance in context against compute, integration, and data, and walks the five-question framework across all of them.

Registering a Citadel Agent in the AI Registry: Azure API Center

This is the final post in a five-part series on the Microsoft Foundry Citadel Platform about Azure API Center AI agent registration. If you landed here first, a quick catch-up helps.

In Part 1, I deployed the Citadel Governance Hub (APIM, Azure OpenAI, Content Safety, Cosmos DB, Logic App) and an Agent Spoke (AI Foundry, Cosmos DB, Key Vault, App Config) in Sweden Central. Next in Part 2, I connected a tool-calling agent through APIM using the standard OpenAI SDK, with the Open-Meteo weather API as its only tool. In Part 3, the agent started writing every run to a conversations container in the spoke’s Cosmos DB. In Part 4, I built a three-layer kill switch so the platform team could shut an agent down fast when something went wrong.

By the end of Part 4, I had a working agent with a gateway, an audit trail, and an off switch. But I still had a gap that only shows up once you stop thinking about one agent and start thinking about a fleet of them: nobody outside my own head knew this agent existed.

That’s the problem this post solves.

Why register agents at all

One agent is easy to track. You know its name, its resource group, and roughly what it does, because you built it last week. Ten agents get harder. A hundred agents, spread across teams, spokes, and environments, becomes a governance problem rather than an inconvenience.

Platform teams eventually ask the same three questions about every agent in the estate: what does it do, what data does it touch, and who owns it. Without a registry, the answer lives in Slack threads, README files, and the memory of whoever built the thing. That doesn’t scale, and it definitely doesn’t survive an audit.

Azure API Center gives the Citadel Platform a place to answer those questions consistently, for every agent, in a format a platform team (or a compliance officer) can actually query. And as you’ll see later in this post, that same discipline lines up neatly with a transparency obligation that’s no longer theoretical: Article 49 of the EU AI Act.

Azure API Center 101

API Center and Azure API Management solve different problems, even though they sound similar and live in the same governance hub.

APIM is a runtime gateway. It sits in the request path, enforces policies, and does the actual work of routing, throttling, and inspecting traffic. That’s where the kill switch from Part 4 lives, and it’s where the five governance policies from earlier in the series get enforced.

API Center is a catalog. It doesn’t sit in the request path at all. Its job is to hold structured metadata about APIs and, in our case, agents: what they are, what version they’re on, who owns them, and what environment they run in. Think of APIM as the checkpoint and API Center as the registry office.

In the hub resource group rg-ai-hub-gateway-dev, the API Center instance is already deployed as apic-wpvlimv4ngkns, alongside APIM. That placement matters. The registry belongs in the hub because it needs to see across every spoke, not just the one running our weather agent.

The AI Publish Contract pattern

A generic API definition (an OpenAPI spec, for example) tells you the shape of the requests and responses. It doesn’t tell you whether the thing behind that shape is calling a third-party model, touching personal data, or making decisions that affect a person’s rights.

For agents, that gap matters more than it does for a plain REST API. So the Citadel Platform defines a small, structured metadata contract that every agent has to carry before it gets registered. I call it the AI Publish Contract. It rides alongside the API definition in API Center as custom metadata, rather than replacing the API definition entirely.

Here’s what it looks like for the weather agent:

json

{
"contractVersion": "1.0",
"agent": {
"id": "citadel-weather-agent-v1",
"displayName": "Citadel Weather Agent",
"description": "Tool-calling agent that answers weather queries using Open-Meteo as its only external tool.",
"owner": "platform-team@example.com",
"environment": "dev",
"spokeResourceGroup": "rg-ai-spoke-dev",
"aiFoundryProject": "aifp-tggi2gmkw22w4"
},
"modelBacking": {
"provider": "azure-openai",
"routedThrough": "apim-wpvlimv4ngkns",
"model": "gpt-4o-mini"
},
"tools": [
{
"name": "open-meteo-forecast",
"type": "external-api",
"endpoint": "https://api.open-meteo.com/v1/forecast",
"dataClassification": "public"
}
],
"dataClassification": "public",
"personalDataProcessed": false,
"highRiskCategory": false,
"killSwitchLayer": "named-value-flip",
"transparencyDisclosure": {
"aiActArticle49Applicable": false,
"userFacingDisclosureRequired": true,
"disclosureText": "This response was generated by an AI system."
}
}

A few fields deserve a comment, because I went back and forth on them.

dataClassification and personalDataProcessed exist because a registry that doesn’t record data sensitivity is only half a registry. The weather agent is a deliberately boring example: no personal data, no high-risk category, public weather data in and out. That’s exactly why it’s a good agent to demonstrate the pattern on before you register something that touches real user data.

transparencyDisclosure maps directly to the Article 49 discussion later in this post. Even for a low-risk agent, I keep the field in the contract rather than making it conditional. Consistency across every agent in the registry is the whole point.

Step-by-step: registering the weather agent in Azure API Center

With the contract defined, registration itself is a handful of steps. I’ll show the CLI first, then the portal equivalent, because I use both depending on whether I’m scripting a pipeline or walking a colleague through it live.

Prerequisites

You need an existing API Center instance (ours is apic-wpvlimv4ngkns in rg-ai-hub-gateway-dev) and Contributor access to it. If you’re following along, confirm the instance is visible first:

az apic show --resource-group rg-ai-hub-gateway-dev \
--name apic-wpvlimv4ngkns \
--subscription dc0f4d72-3734-4b03-8884-ccfb9c2c4cc7

Step 1: define the custom metadata schema

API Center lets you extend its metadata model with custom fields. Before registering any agent, define a schema for the AI Publish Contract so every future registration validates against the same structure.

az apic metadata create --resource-group rg-ai-hub-gateway-dev \
--service-name apic-wpvlimv4ngkns \
--metadata-name aiPublishContract \
--schema "{\"type\":\"string\",\"title\":\"AI Publish Contract\"}"
--assignments "[{\"entity\":\"api\",\"required\":true}]"

I keep ai-publish-contract-schema.json as a JSON Schema file that mirrors the structure shown above. Doing this once means every agent registered afterward gets validated the same way, instead of drifting field by field as different teams register their own agents.

Step 2: register the agent as an API

az apic api create --resource-group rg-ai-hub-gateway-dev \
--service-name apic-wpvlimv4ngkns \
--api-id citadel-weather-agent-v1 \
--title "Citadel Weather Agent" \
--type rest \
--custom-properties @C:\Users\steef\ai-citadel-agent\citadel-agent\ai-publish-contract.json

The --type rest flag looks like a mismatch at first, since this is an agent, not a REST API in the traditional sense. API Center doesn’t yet have a native “agent” type, so I register it as a REST API and let the AI Publish Contract metadata carry the agent-specific detail. That’s a workaround, not a limitation I’d defend as elegant, and I expect this to get cleaner as Microsoft extends API Center for agentic workloads.

Step 3: create the API version

az apic api version create --resource-group rg-ai-hub-gateway-dev \
--service-name apic-wpvlimv4ngkns \
--api-id citadel-weather-agent-v1 \
--version-id v1-0 \
--title "v1" \
--lifecycle-stage development

Step 4: attach the definition

az apic api definition create --resource-group rg-ai-hub-gateway-dev \
--service-name apic-wpvlimv4ngkns \
--api-id citadel-weather-agent-v1 \
--version-id v1-0 \
--definition-id openapi \
--title "OpenAPI"

Then import the actual spec, which describes the agent’s HTTP-facing invocation contract (the endpoint APIM exposes, not the internal tool-calling logic):

az apic api definition import-specification --resource-group rg-ai-hub-gateway-dev \
--service-name apic-wpvlimv4ngkns \
--api-id citadel-weather-agent-v1 \
--version-id v1-0 \
--definition-id openapi \
--format link
--value "https://apim-wpvlimv4ngkns.azure-api.net/weather-agent/openapi.json"
--specification "{\"name\":\"openapi\",\"version\":\"3.0.0\"}"

That failed immediately:

(ValidationError) Could not download the API specification from
the specified URL due to an error:
Could not reach the provided URL.
Please make sure that the specification file
is publicly available and that the link is valid and up-to-date.

My first assumption was a network or auth issue between API Center and APIM. It wasn’t. Curling the same URL directly returned a plain 404 from APIM itself:

{ "statusCode": 404, "message": "Resource not found" }

That 404 is the real signal. There is nothing published at that path, because there was never a dedicated API resource for the weather agent in APIM to begin with. Part 2 wired the agent up through APIM as a passthrough to Azure OpenAI, using the standard OpenAI SDK. That gets you routing, policy enforcement, and governance on every call, but it doesn’t give you a discoverable, documented API surface. A passthrough proxies requests. It doesn’t publish an OpenAPI spec describing them.

This is worth sitting with for a second, because it’s not just a command-line gotcha. It means the OpenAPI definition API Center wants isn’t something you can export from a running agent. You have to author it, the same way you’d write documentation, because the system underneath genuinely has nothing to hand you.

So the real fix has two parts. First, write a minimal OpenAPI spec that documents the agent’s actual invocation contract as it exists through the passthrough:

{
"openapi": "3.0.0",
"info": {
"title": "Citadel Weather Agent",
"version": "1.0.0",
"description": "Tool-calling agent invocation contract, routed through APIM to Azure OpenAI."
},
"servers": [
{ "url": "https://apim-wpvlimv4ngkns.azure-api.net" }
],
"paths": {
"/openai/deployments/{deploymentId}/chat/completions": {
"post": {
"summary": "Invoke the weather agent",
"parameters": [
{
"name": "deploymentId",
"in": "path",
"required": true,
"schema": { "type": "string" }
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"messages": { "type": "array" },
"tools": { "type": "array" }
},
"required": ["messages"]
}
}
}
},
"responses": {
"200": {
"description": "Chat completion response, including any tool calls the agent made.",
"content": {
"application/json": {
"schema": { "type": "object" }
}
}
}
}
}
}
}
}

Save that locally as openapi.json, then import it inline instead of by link, since there’s nothing at the other end of a link to point to:

az apic api definition import-specification --resource-group rg-ai-hub-gateway-dev \
--service-name apic-wpvlimv4ngkns \
--api-id citadel-weather-agent-v1 \
--version-id v1-0 \
--definition-id openapi \
--format inline \
--value @C:\Users\steef\ai-citadel-agent\citadel-agent\openapi.json \
--specification "{\"name\":\"openapi\",\"version\":\"3.0.0\"}

That worked. But notice what actually happened here. The spec isn’t extracted from the system, it’s a description you write and commit to keeping accurate. If you add a second tool to the agent, or change the invocation shape, this file goes stale unless you treat it as part of the change, not an afterthought to registration.

Step 5: register the environment and deployment

az apic environment create \
--resource-group rg-ai-hub-gateway-dev \
--service-name apic-wpvlimv4ngkns \
--environment-id dev \
--title "Development" \
--type development
az apic api deployment create --resource-group rg-ai-hub-gateway-dev \
--service-name apic-wpvlimv4ngkns \
--api-id citadel-weather-agent-v1 \
--deployment-id dev-deployment \
--title "Dev deployment" \
--environment-id "/workspaces/default/environments/dev" \
--definition-id "/workspaces/default/apis/citadel-weather-agent-v1/versions/v1-0/definitions/openapi" \
--server "{\"runtimeUri\":[\"https://apim-wpvlimv4ngkns.azure-api.net/weather-agent\"]}"

The portal equivalent FOR Azure API Center

If you’d rather click through it, the same five steps map onto the portal like this. Open the API Center resource in the Azure portal, and go to APIs. Select “Register API,” fill in the title and type, and paste the AI Publish Contract JSON into the custom metadata panel that appears once the metadata schema is assigned. From there, add a version, upload or link the OpenAPI definition, and finish by adding an environment and a deployment under the API’s Deployments tab.

I default to the CLI for anything I’ll repeat across agents, and the portal when I’m registering something once or explaining the process to someone new to the platform.

What it looks like in the fleet dashboard

Once registration finishes, the weather agent shows up in the Azure API Center catalog alongside anything else registered in the hub. The catalog view lists each API (agent) with its title, type, and lifecycle stage, and clicking into it surfaces the custom metadata panel with the full AI Publish Contract attached.

This is where the payoff becomes visible. Instead of a spreadsheet someone updates twice a year, the platform team gets a live, filterable view. Filter by dataClassification: personal and you instantly see which agents touch sensitive data. In addition, filter by environment: production and you get the actual production fleet, not the fleet someone remembers deploying. And finally, filter by owner, and you know exactly who to page when an agent misbehaves.

For a single weather agent, this looks like overkill. It isn’t the weather agent that justifies the pattern. It’s the fortieth agent, registered by a team you’ve never met, that you’ll be glad has the same contract structure as this one.

Connecting to EU AI Act Article 49

Article 49 of the EU AI Act introduces a registration obligation, primarily for providers and deployers of high-risk AI systems, who must register certain information in an EU database before those systems go into service or are used. The exact scope depends on the system’s risk classification, and I’m not going to pretend to give legal advice here. Talk to your compliance team about whether a specific system falls under that obligation.

What I can talk about is the architecture. The AI Publish Contract pattern doesn’t make an agent compliant with Article 49. What it does is put your organization in a position to answer an Article 49-style question quickly: which AI systems exist, what do they do, what data do they process, and are any of them high-risk. That’s the same question a platform team asks internally for entirely operational reasons, and it turns out to be the same question a regulator asks for entirely different reasons.

Building the registry now, before any single agent forces the issue, means the answer already exists when someone asks. Retrofitting a registry across dozens of agents that were never designed with one in mind is a much worse project to inherit.

The weather agent in this post sets aiActArticle49Applicable: false, because it’s a low-risk, non-personal-data agent. The field exists precisely so the next agent, the one that does touch personal data or make consequential decisions, has a place to say true and trigger whatever process your organization builds around that flag.

Pitfalls

A few things caught me off guard while working through this. I’m flagging these as the kind of issues I’d expect to hit on a real rollout, so treat them as a checklist to verify against your own environment rather than a transcript of my exact errors.

  • A passthrough agent has no OpenAPI spec to export, even though it looks like it should. This is the one that actually got me. Importing by link failed with a vague reachability error from az apic, which pointed me toward a networking explanation. Curling the URL directly gave the real answer: a plain 404 Resource not found from APIM. There was nothing published at that path, because Part 2 wired the agent up as a passthrough to Azure OpenAI, not as a dedicated API resource with its own documented surface. Passthroughs proxy requests, they don’t publish specs. The fix is to hand-author a minimal OpenAPI spec describing the invocation contract and import it inline, and then treat that file as something you update deliberately when the agent’s interface changes, not something the platform keeps in sync for you.
  • Custom metadata schema assigned after APIs already exist. If you register an API before creating the metadata schema, the custom properties won’t validate retroactively. Define the schema first, every time, or you’ll end up patching APIs one by one afterward.
  • RBAC gaps across the hub-spoke boundary. The identity registering the agent needs Contributor (or a custom role with equivalent permissions) on the API Center instance in the hub, which is a different resource group than where the agent itself runs. Teams that only have access to their own spoke will hit a permissions wall here, and that’s worth deciding on deliberately rather than discovering it during a rollout.

Other Pitfalls

  • Confusing API Center registration with APIM configuration. These are separate systems that happen to sit in the same hub. Registering an agent in API Center doesn’t change how APIM routes or governs it. I’ve seen (and made) the assumption that registration alone would trigger some policy change in APIM. It doesn’t. They’re linked by convention and shared metadata, not by any automatic sync.
  • Definition drift. The OpenAPI definition describes the HTTP surface of the agent, but the AI Publish Contract describes its behavior and data handling. Nothing enforces that these stay in sync when the agent changes. Treat both as part of your deployment pipeline, updated together, rather than the contract as a one-time registration artifact.
  • Lifecycle stage mismatches. Marking an API’s --lifecycle-stage as development when it’s actually running in a shared dev/test environment used by multiple teams creates confusion during the fleet review. Match lifecycle stages to how the environment is actually used, not just what resource group it happens to sit in.

What’s next

This closes out the deployment arc of the series: hub and spoke, a working agent, persistence, a kill switch, governance policies, and now a registry entry. What I haven’t covered yet, and what comes next, is hardening this for anything beyond a single dev environment. That means promotion paths from dev to staging to production, cost controls that scale with a growing agent fleet, and staging environment parity so what you test actually matches what you ship.

If there’s interest, that hardening work is a natural follow-up series rather than a single post, given how much ground it covers.

Wrapping up the series

Five posts ago, this started as an empty resource group in Sweden Central. It’s now a governed platform: a hub that enforces policy and holds the registry, a spoke that runs the agent, a kill switch that can shut things down fast, and a contract-based registration pattern that keeps the fleet auditable as it grows.

The weather agent was never really the point. It was the simplest possible agent I could use to prove out every layer of the platform without the complexity of a real production workload getting in the way. The patterns here, hub-spoke isolation, layered kill switches, policy-driven governance, and contract-based registration, are the parts meant to outlast this specific agent.

Thanks for following along through the series. If you’re building something similar, I’d like to hear what broke for you.

Knowledge Base as a Service in Azure Logic Apps

The Logic Apps Agent Loop series published here between May and June 2026 documented the agentic capabilities of Azure Logic Apps in depth. Notably, one recurring limitation across that series was the complexity of knowledge retrieval: building a proper Retrieval-Augmented Generation pipeline required a separately configured Azure AI Search index, an indexer, a data source, and a significant setup overhead before the agent could answer a question from enterprise content.

Consequently, the Knowledge Base-as-a-Service (KBaaS) capability in Azure Logic Apps, announced at Integrate 2026 and now in preview, addresses this directly. This post, as a follow-up to the previous post on all the announcements, walks through what KBaaS is, how it works, and how to build a practical HR policy agent that answers employee questions from uploaded policy documents, with a complete sample available on GitHub.

What is Knowledge Base as a Service?

KBaaS is a Logic Apps-native RAG pipeline that sits in front of Azure Cosmos DB and Azure OpenAI. Instead of building and managing the ingestion and retrieval infrastructure yourself, you upload documents and KBaaS handles the rest.

Specifically, the service has two pipelines:

  • Ingestion pipeline — when you upload a document, KBaaS automatically parses, chunks, summarises, and vectorises the content using your Azure OpenAI embeddings model. The results are stored in four Cosmos DB containers that KBaaS creates and manages on your behalf.
  • Retrieval pipeline — when an agent loop queries the knowledge base, KBaaS rewrites the query if needed, generates a vector representation, performs a semantic search against Cosmos DB, and returns the most relevant chunks to the language model for response generation.
Structural diagram showing the two KBaaS pipelines. The ingestion pipeline flows left to right: document upload, parse, chunk, summarise, embed, then into Cosmos DB across four containers. A dashed arrow connects the stored vectors in Cosmos DB down to the retrieval pipeline. The retrieval pipeline flows left to right: agent loop query, rewrite, vector search against Cosmos DB, top ranked chunks, LLM response. The bottom row shows the Logic Apps agent loop wrapper: HTTP trigger, knowledge base tool, Response action.
Figure 1 — The two pipelines that power Knowledge Base as a Service in Azure Logic Apps. The ingestion pipeline (top) runs automatically when you upload a document: it parses and extracts text, chunks it into segments, summarises using Azure OpenAI, and embeds the content using an embeddings model before storing everything in four Cosmos DB containers. The retrieval pipeline (middle) runs when the agent loop queries the knowledge base: the query is rewritten if needed, vectorized, and matched against the stored embeddings in Cosmos DB via semantic search. The top-ranked chunks are passed to the LLM to generate a grounded answer. The agent loop wrapper (bottom) shows how these two pipelines sit inside a standard Logic Apps autonomous workflow.

In contrast, the key difference from a manually configured RAG pipeline is the abstraction layer. You do not configure chunking strategies, embedding dimensions, index schemas, or retrieval parameters. Instead, you upload documents and add the knowledge base as a tool. The agent loop does the rest.

Knowledge Base as a Service: Deploying the sample

The complete sample is available at steefjan1/logic-apps-kbaas-sample. It includes the workflow definition, Bicep infrastructure template, sample HR policy documents, and connection configuration.

Step 1: Provision infrastructure with azd

cd C:\Dev\logic-apps-kbaas-sample
azd auth login
azd init
azd env new <your-env-name>
azd provision

Naming note: avoid hyphens in your environment name. The Bicep template appends store to the prefix for the storage account name, and hyphens are not valid in storage account names. Use a short alphanumeric name such as kbaas rather than kbaas-preview.

Specifically, azd provision creates the resource group, a Logic App Standard, a Cosmos DB with vector search enabled, a storage account, an App Service Plan, and RBAC role assignments.

Step 2: Set app settings

$cosmosKey = az cosmosdb keys list `
--name <cosmos-name> `
--resource-group <resource-group> `
--query primaryMasterKey -o tsv
$storageKey = az storage account keys list `
--account-name <storage-name> `
--resource-group <resource-group> `
--query "[0].value" -o tsv
az logicapp config appsettings set `
--name <logic-app-name> `
--resource-group <resource-group> `
--settings `
AzureWebJobsStorage="DefaultEndpointsProtocol=https;AccountName=<storage-name>;AccountKey=$storageKey;EndpointSuffix=core.windows.net" `
FUNCTIONS_EXTENSION_VERSION="~4" `
FUNCTIONS_WORKER_RUNTIME="dotnet" `
APP_KIND="workflowapp" `
OPENAI__endpoint="https://<your-aoai-resource>.openai.azure.com/" `
OPENAI__key="<your-aoai-key>" `
agent_openAIKey="<your-aoai-key>" `
agent_openAIEndpoint="https://<your-aoai-resource>.openai.azure.com/" `
COSMOS__endpoint="https://<cosmos-name>.documents.azure.com:443/" `
COSMOS__key="$cosmosKey"

Step 3: Deploy the workflow

Compress-Archive `
-Path connections.json, host.json, hr-policy-agent `
-DestinationPath ..\kbaas-deploy.zip `
-Force
az logicapp deployment source config-zip `
--name <logic-app-name> `
--resource-group <resource-group> `
--subscription "<your-subscription>" `
--src ..\kbaas-deploy.zip
az logicapp restart `
--name <logic-app-name> `
--resource-group <resource-group>

Verify the workflow deployed cleanly go to the Logic App OverviewNotifications tab and confirm no WorkflowProcessingFailed errors appear before proceeding.

Step 4: Create the knowledge base connection

In the Azure portal, open your Logic App. Under Agents in the left sidebar, select Knowledge base then + Set up.

Basics tab:

  • Display name: hr-knowledge-base
  • Authentication type: Key-based
  • Database: select your Cosmos DB account — URL endpoint and key auto-populate
  • Click Next

Model tab:

  • Authentication type: URL and key-based authentication
  • Azure OpenAI resource: select your Azure OpenAI resource
  • Completions model: enter your GPT-4o deployment name exactly as it appears in Azure OpenAI
  • Embeddings model: text-embedding-3-small
  • Click Create

Preview limitation: The knowledge base connection does not persist across page refreshes in the current preview — this is a known portal UI bug. Do not refresh the page after clicking Create. Proceed immediately to adding files.

Note: Furthermore, after creating the connection, you can only edit display names. Authentication type, endpoint, and model deployment names cannot be changed. Get these right before clicking Create.

Step 5: Upload knowledge sources

Immediately after creating the connection, without refreshing, click New → Add files.

  • Group name: type hrpolicies
  • Upload hr-leave-policy.md from the sample’s docs/ folder
  • Artifact name: hr-leave-policy
  • Click Add

Once the status changes to Completed, then repeat for hr-expense-policy.md.

Preview limitation: The portal uploads one file at a time. Wait for each file to reach Completed before uploading the next.

Preview limitation: The Add button may remain greyed out if the artifact name field does not register keyboard input. If this happens, try dragging and dropping the file onto the upload area rather than using the file picker, then type the artifact name. If the button remains inactive, try a private/incognito browser window.

Azure portal Knowledge base page for the kbass-preview-la Standard logic app. The page shows the hrpolicies group containing two files: hr-leave-policy and hr-expense-policy, both showing Completed status after KBaaS ingestion processing.
Figure 2 — Both HR policy documents successfully ingested into the knowledge base. The KBaaS ingestion pipeline parsed, chunked, summarised, and vectorised each document automatically no manual configuration of Cosmos DB containers, indexers, or embedding pipelines required. The Completed status confirms the content is ready for semantic retrieval by the agent loop.

KBaaS creates the following Cosmos DB containers during ingestion:

ContainerPurpose
KnowledgeHubsKnowledge base metadata
KnowledgeArtifactsSource metadata and document references
KnowledgeArtifactChunksFull-text document chunks
KnowledgeArtifactChunkSummariesSummarised chunks with vector embeddings

Step 6: Add the knowledge base as a tool

Open the hr-policy-agent workflow in the Logic Apps designer. Select the HR Policy Agent action and scroll to the Knowledge base section in the parameters pane. Select Create and choose hr-knowledge-base from the list. Save the workflow.

Testing the agent

Get the trigger URL from hr-policy-agentOverviewRun trigger → copy the callback URL.

Test 1 — grounded answer from the leave policy:

In postman execute: https://kbass-preview-la.azurewebsites.net:443/api/hr-policy-agent/triggers/When_an_HTTP_request_is_received/invoke?api-version=2022-05-01&sp=%2Ftriggers%2FWhen_an_HTTP_request_is_received%2Frun&sv=1.0&sig=<sig>

With payload (application\json): {“question”: “How many days of annual leave am I entitled to?”}

Postman request showing a POST to the hr-policy-agent trigger URL with body containing question: How many days of annual leave am I entitled to? The response shows 200 OK with a JSON body containing the question and a grounded answer citing the annual leave policy document.
Figure 3 — The HR policy agent returns a grounded answer to a leave entitlement question. The agent queried the knowledge base, retrieved the relevant section from the annual leave policy document, and returned a cited response, all within a single autonomous agent loop run. The answer is grounded in the uploaded policy content rather than the model’s general training knowledge.

Test 2 — grounded answer from the expense policy:

In postman execute: https://kbass-preview-la.azurewebsites.net:443/api/hr-policy-agent/triggers/When_an_HTTP_request_is_received/invoke?api-version=2022-05-01&sp=%2Ftriggers%2FWhen_an_HTTP_request_is_received%2Frun&sv=1.0&sig=<sig>

With payload: (application\json):{“question”: “What is the maximum I can claim per night for hotel accommodation?”}

Test 3 — correct fallback when knowledge base has no answer:

In postman execute: https://kbass-preview-la.azurewebsites.net:443/api/hr-policy-agent/triggers/When_an_HTTP_request_is_received/invoke?api-version=2022-05-01&sp=%2Ftriggers%2FWhen_an_HTTP_request_is_received%2Frun&sv=1.0&sig=<sig>

With payload: {“question”: “What is the company policy on remote working?”}

As a result, the agent answers the first two questions from the uploaded documents and correctly declines the third exactly as the system prompt instructs.

Logic Apps run history for the hr-policy-agent workflow showing a successful run. The log panel shows the HTTP trigger, a user chat message, the agent action completing with tool invocations including the knowledge base retrieval tool, a sent chat message, and the Response action. The canvas shows the workflow steps with green success indicators.
Figure 4 — The run history of the HR policy agent showing the full agent loop execution. The knowledge base tool invocation is visible as a discrete step within the agent iterations: the agent reasoned over the question, decided to query the knowledge base, received the relevant policy chunks, and composed a grounded answer before returning the Response action result. This is the same Think → Act → Observe loop covered in the Logic Apps Agent Loop series, now with KBaaS providing the retrieval step automatically.

Knowledge Base as a Service:: Practitioner notes

Several things in the current preview are worth documenting for anyone following along:

Connection persistence bug — the knowledge base connection disappears on page refresh in the portal. The connection is correctly stored in connections.json on disk and the workflow runs correctly — but the portal UI does not display it after refresh. Work around this by not refreshing after creating the connection and uploading files in the same browser session.

knowledgeHubConnections format — the connections.json format for knowledge base connections is not publicly documented. If you include a knowledgeHubConnections block with incorrect structure in your deployed connections.json, the Logic App runtime will fail to start with The 'knowledgeHubConnections' property in connection.json has a value that cannot be parsed. The safest approach is to omit knowledgeHubConnections from your deployed connections.json entirely and let the portal write it after deployment.

Embeddings model region availabilitytext-embedding-3-small with Standard SKU is not available in all Azure regions. Use GlobalStandard SKU as shown in the prerequisites command above.

Managed Identity for agent connections — KBaaS supports Managed Identity authentication for the Cosmos DB connection. However, Managed Identity is not supported for the agent model connection when using the MicrosoftFoundry model type. If your workflows use Foundry Models, the agent connection must use Key authentication. Managed Identity remains the right choice for the Cosmos DB connection and all other backend services.

workflow.json schema — the Agent action requires a limit property at the action level (not inside inputs) and a modelConfigurations block with a referenceName pointing to the agent connection. The Microsoft Learn documentation does not show these as required, but the runtime rejects the workflow without them.

Preview limitations

  • Only uploaded files are supported as knowledge sources — live API connectors and SharePoint libraries are not yet available
  • Supported formats: DOC, DOCX, HTML, MD, PDF, PPT, PPTX, TXT, XLS, XLSX
  • Text-based content only — images within documents are not parsed
  • Default chunking only — custom chunk size and overlap configuration is not yet available
  • Azure portal only — VS Code is not yet supported for knowledge base configuration
  • One file upload at a time — wait for each file to reach Completed before uploading the next

GitHub sample

The complete sample workflow definition, Bicep infrastructure template, sample HR policy documents, and connection configuration are available on my GitHub.

What comes next

KBaaS is the most significant reduction in RAG pipeline complexity that Logic Apps has offered to date. For scenarios where uploaded documents are the primary knowledge source, it removes the Azure AI Search setup entirely and replaces it with a portal-native upload flow. The preview limitations, particularly the connection persistence bug and the lack of live source connectors, will be addressed in subsequent releases.

The natural next step is combining KBaaS with the multi-agent patterns from Part 5 of the Logic Apps Agent Loop series, an orchestrator agent that routes questions to specialist knowledge bases, each containing documents for a specific domain.

Azure Logic Apps at Integrate 2026: The Announcements

Integrate 2026 took place on June 8–9 and brought the Microsoft integration product group together with the community for the first time since the platform’s agentic capabilities became generally available. For Azure Logic Apps, the announcements from Divya Swarnkar and Wagner Silveira’s session “What’s New and What’s Next in Azure Logic Apps” signal something more than a feature release cycle. They signal a platform repositioning.

Historically, Logic Apps has always occupied the integration and workflow orchestration layer of the Azure stack. Consequently, it is also firmly in the AI orchestration layer, connecting systems, knowledge, and intelligent agents in ways that were not possible twelve months ago. As a result, this post unpacks the five announcements that matter most for integration architects and connects them to the work covered in the Logic Apps Agent Loop series published here over the past two months.

Azure Logic Apps Integrate: The announcements

1. Azure Logic Apps Automation

The headline announcement is Logic Apps Automation, a new managed offering that sits alongside Logic Apps Standard and Consumption. It introduces a dedicated automation portal, AI-assisted workflow authoring, and a fully managed infrastructure model that removes the App Service Plan configuration and management required by Standard.

For integration architects in particular, this is significant in two ways. First, AI-assisted authoring lowers the barrier to building workflows; natural language descriptions of what a workflow should do can generate a starting point for the designer. Second, the fully managed model means organizations can adopt Logic Apps at scale without dedicated infrastructure expertise for every deployment.

In practice, Logic Apps Automation targets the enterprise automation use case: the high-volume, repeatable processes that currently live in RPA tools, home-grown scripts, or overly complex BPMN platforms. Importantly, it retains Logic Apps’ governance and security capabilities while making the platform accessible to a broader audience within the organization.

2. Knowledge as a Service

Microsoft announced Knowledge as a Service for Logic Apps, a capability that simplifies how organizations prepare enterprise data for AI-driven scenarios. Rather than building and maintaining complex data ingestion, chunking, embedding, and retrieval pipelines, teams can upload content, and Logic Apps handles the orchestration required to make that data available to AI agents.

Notably, this is directly relevant to the agentic workflows covered in this series. In Post 4, the agent tool layer relied on Azure AI Search as the retrieval mechanism, which required a separately configured search index, an indexer, a data source, and a skill set. Instead, Knowledge as a Service abstracts that complexity into a Logic Apps-native capability, reducing the setup time for a retrieval-augmented generation pattern from hours to minutes.

Significantly, the capability will be available across both Logic Apps Automation and Logic Apps Standard.

3. Azure AI Foundry Agent Integration

Logic Apps now supports invoking Azure AI Foundry Agents directly from workflows. Organisations can build, evaluate, and govern agents within Azure AI Foundry and use Logic Apps to orchestrate those agents as part of broader business processes.

Crucially, this closes a gap that the agent loop series ran into directly. Moreover, in Post 4, the attempt to call a Logic Apps workflow as an OpenAPI tool from Foundry hit network restrictions between the two platforms. As a result, the native Foundry Agent Integration announced at Integrate 2026 addresses this at the platform level; the connection between Logic Apps and Foundry is a first-class integration, not a custom OpenAPI workaround.

In practice, for multi-agent architectures, this means the orchestrator-worker pattern from Post 5 can now span both platforms: a Foundry agent as the orchestrator, Logic Apps autonomous workflows as the workers, with native connectivity between them rather than the SAS token-based HTTP invocation used in the demo.

4. Logic Apps Standard SDK

Microsoft introduced the Logic Apps Standard SDK, enabling workflows to be authored directly in C#. Developers gain access to familiar .NET tooling, type safety, NuGet packaging, and proper source control practices, while continuing to use the existing Logic Apps runtime and operational infrastructure.

Of all the announcements, this is the most relevant to the DevOps content in Post 7 of this series. The JSON-on-disk deployment model covered there remains valid, but the SDK adds a code-first authoring path that developer teams will strongly prefer for complex workflows. Type-safe workflow definitions, unit testability, and IDE integration (Visual Studio, VS Code) address the most common developer friction points with the current designer-first model.

For integration architects evaluating Logic Apps for new projects, the SDK changes the “who builds this” conversation. Workflows no longer need to be designer-authored by integration specialists; they can be written by developers using the tools they already know.

5. Azure Connector Namespace

Microsoft unveiled Azure Connector Namespace, which decouples Logic Apps connectors from Logic Apps workflows. The connector ecosystem, which includes over 1,400 connectors covering Microsoft and third-party services, can now be used from custom applications, Azure Functions, Container Apps, and AI agent platforms without the workflow runtime.

Of the five, this is architecturally the most significant for the longer term. Previously, the three-layer tool model covered in Post 4 (built-in connectors, custom connectors, MCP servers) assumed that connectors lived inside Logic Apps workflows. Azure Connector Namespace removes that constraint. Now, an Azure Function or a Foundry agent can now consume a Logic Apps connector directly, accessing Office 365, Service Bus, SAP, or any of the other 1,400+ services without a workflow in between.

Side-by-side diagram showing the architectural shift introduced by Azure Connector Namespace. Left side labelled Before shows connectors and agent tools locked inside a Logic Apps workflow boundary, with the workflow runtime required for all connector use, and a coral box at the bottom indicating connectors are locked to the Logic Apps runtime only. Right side labelled After shows a shared Azure Connector Namespace layer at the top containing Office 365, SAP, Service Bus, and 1,400 plus connectors, with four consumers below it connected by arrows: Logic Apps workflows, Azure Functions serverless compute, Container Apps custom apps, and AI agents including Foundry, MCP, and custom. A teal box at the bottom indicates connectors are decoupled from the workflow runtime and usable anywhere.
Figure 1 — The architectural shift introduced by Azure Connector Namespace. Before the announcement (left), connectors were tightly coupled to the Logic Apps workflow runtime, accessible only from within a workflow, with the runtime always in the execution path. After (right), the connector ecosystem becomes a shared infrastructure layer. Logic Apps workflows, Azure Functions, Container Apps, and AI agents, whether running in Azure AI Foundry, via MCP, or as custom implementations, can all consume the same 1,400+ connectors independently of the workflow runtime.

For enterprise AI architectures, this means the connectivity layer and the orchestration layer are now separable. An AI agent can reach any enterprise system through the connector ecosystem without Logic Apps being the runtime that executes the connection.

Azure Logic Apps Integrate: The direction of travel

Taken together, the five announcements describe a platform moving in a consistent direction: Logic Apps is becoming the connectivity and orchestration substrate for enterprise AI, not just enterprise integration.

The diagram below maps the five announcements against the platform layers they affect: authoring, orchestration, knowledge, connectivity, and developer experience.

Structural diagram showing five Integrate 2026 Logic Apps announcements organised into three platform layers. Layer 1 authoring and developer experience contains Logic Apps Automation with AI-assisted authoring and fully managed infrastructure, and the Logic Apps Standard SDK with C# workflow authoring and .NET tooling. Layer 2 orchestration and AI contains Azure AI Foundry Agent Integration spanning the full width, enabling native invocation of Foundry agents from workflows without an OpenAPI workaround. Layer 3 knowledge and connectivity contains Knowledge as a Service with RAG pipeline abstraction, and Azure Connector Namespace giving access to 1,400 plus connectors without the workflow runtime. A series connection row at the bottom links the announcements to Posts 4, 5, and 7 of the Logic Apps Agent Loop series.
Figure 2 — Five announcements from the Logic Apps product group session at Integrate 2026, mapped to the platform layers they affect. Layer 1 addresses the authoring and developer experience gap. Logic Apps Automation brings AI-assisted workflow creation and a fully managed infrastructure model, while the Standard SDK opens a code-first C# path for development teams. Layer 2 closes the orchestration gap between Logic Apps and Azure AI Foundry with a native agent integration that removes the OpenAPI workaround documented in Post 4 of this series. Layer 3 extends the platform’s reach: Knowledge as a Service abstracts RAG pipeline complexity, and Azure Connector Namespace decouples the 1,400+ connector ecosystem from the workflow runtime entirely.

The agent loop series documented the platform as it stood at the general availability of the agentic capabilities. Encouragingly, several of the limitations called out in that series the Foundry network restrictions, the complexity of knowledge retrieval setup, and the JSON-only authoring model are directly addressed by the Integrate 2026 announcements. That is a healthy sign: the platform team is hearing the practitioner feedback and moving quickly.

Azure Logic Apps Integrate: What this means for integration architects

Three practical implications for architects evaluating or already using Logic Apps:

  • First, revisit your hosting model decision. Logic Apps Automation changes the Standard-versus-Consumption decision for new projects. If the fully managed model meets your governance requirements, the App Service Plan overhead goes away.
  • Secondly, reconsider your knowledge retrieval approach. If you are building RAG patterns on Azure today using manually configured AI Search indexes, Knowledge as a Service is worth evaluating as a simpler path, particularly for projects where the data preparation pipeline is more complex than the agent itself.
  • Third, plan for SDK adoption. If your organization has strong .NET development capability, the Logic Apps Standard SDK should be on the evaluation list for any new workflow project. The designer-first model remains valid, but the code-first path will be preferred by development teams working in existing C# codebases.

Azure Logic Apps Integrate: Series connection

The Logic Apps Agent Loop series published here between May and June 2026 covered the agentic capabilities of Logic Apps in depth, from the anatomy of a single agent loop through to multi-agent patterns, security, and production operations. The Integrate 2026 announcements build directly on that foundation. Post 4’s MCP server pattern connects to the Azure Connector Namespace. Subsequently, Post 5’s orchestrator-worker pattern connects to the Foundry Agent Integration. Post 7’s DevOps section connects to the Standard SDK.

Microsoft Foundry Citadel Platform Azure: A Practitioner’s Deployment Guide

Microsoft Foundry Citadel Platform on Azure is a layered AI governance architecture that delivers production-ready agent deployments with unified governance, end-to-end observability, and centralized policy enforcement via Azure API Management. It is still in preview, and the documentation assumes a degree of familiarity with Azure infrastructure that not everyone has on day one. This post walks through what it actually takes to get a working hub-and-spoke running in Sweden Central, including the pitfalls, so you can decide whether it is a viable starting point for your own AI platform journey.

What Citadel Is (and Is Not)

Before touching the tooling, it helps to understand what Citadel actually deploys. The architecture has four layers:

The first layer — Governance Hub is the runtime enforcement plane: Azure API Management as a centralized AI gateway, Azure API Center as a model registry, and supporting services for content safety, PII detection, cost attribution, and usage telemetry.

Subsequent second layer 2 — AI Control Plane provides observability via the Foundry Control Plane: agent-level execution traces, AI evaluations in development and production, red-teaming, drift monitoring, and fleet dashboards.

The next third layer — Agent Identity transforms agents into managed enterprise assets via Microsoft Entra ID, with lifecycle management, sponsorship models for human accountability, and shadow AI discovery.

Finally, the last fourth layer, 4 Security Fabric, weaves Defender, Purview, and Entra across the other three layers for real-time threat intelligence, data governance, and compliance automation.

For this guide, we deploy Layer 1 (the Governance Hub via the AI Hub Gateway Solution Accelerator) and a Layer 1/2 spoke (via the AI Landing Zone Bicep). Layers 3 and 4 reference existing Azure services (Entra ID, Defender, Purview) that you integrate separately.

Important: Citadel is currently in preview. The repos, parameter schemas, and CLI commands will change. Treat everything in this post as a starting point, not a stable reference.

Prerequisites

Before you start, make sure you have:

  • An Azure subscription with Azure OpenAI access approved (aka.ms/oaiapply)
  • Microsoft.Authorization/roleAssignments/write on the subscription (Owner or User Access Administrator role)
  • Azure CLI installed and authenticated (az login)
  • Azure Developer CLI (azd) installed
  • Node.js — use v20 LTS, not v24. Node 24 on Windows has a known issue where npm bundles are incomplete, causing MODULE_NOT_FOUND errors on npm-cli.js and npm-prefix.js when azd tries to package Logic App components

If you run into npm issues on Windows, the cleanest workaround is Azure Cloud Shell, where Node, npm, az, and azd are all pre-installed and healthy.

Part 1: Deploying the Microsoft Foundry Citadel Governance Hub

Clone the AI Hub Gateway Solution Accelerator:

git clone https://github.com/Azure-Samples/ai-hub-gateway-solution-accelerator.git
cd ai-hub-gateway-solution-accelerator

Create your azd environment:

azd auth login
azd env new ai-hub-gateway-dev
azd env set AZURE_LOCATION swedencentral

Create a parameters file at infra/main.parameters.json. The key decisions:

Model versions matter. At the time of writing, gpt-4o-mini versions 2024-07-18 and 2024-10-18 are retired. Use gpt-4o version 2024-11-20 with GlobalStandard SKU. Always verify current model availability at aka.ms/aoai-regions before deploying these changes frequently.

{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"environmentName": { "value": "ai-hub-gateway-dev" },
"location": { "value": "swedencentral" },
"apimSku": { "value": "Developer" },
"openAiInstances": {
"value": {
"openAi1": {
"name": "openai1",
"location": "swedencentral",
"deployments": [
{
"name": "chat",
"model": { "format": "OpenAI", "name": "gpt-4o", "version": "2024-11-20" },
"sku": { "name": "GlobalStandard", "capacity": 20 }
},
{
"name": "embedding",
"model": { "format": "OpenAI", "name": "text-embedding-3-large", "version": "1" },
"sku": { "name": "Standard", "capacity": 20 }
}
]
}
}
},
"provisionFunctionApp": { "value": false },
"createAppInsightsDashboard": { "value": false },
"enableAIGatewayPiiRedaction": { "value": true },
"enableAIModelInference": { "value": true }
}
}

Deploy:

azd up

Expect 45–90 minutes. APIM Developer SKU is the slow component. If the deployment fails partway through, re-run azd up it is idempotent and will pick up where it left off.

Azure CLI output showing successful deployment of the Microsoft Foundry Citadel Governance Hub including APIM, Azure OpenAI chat and embedding model deployments, private endpoints, and Logic App in Sweden Central.
The AI Hub Gateway Solution Accelerator was deployed successfully in Azure Sweden Central after 21 hours and31 minutes, provisioning APIM, Azure OpenAI, Content Safety, Application Insights, private endpoints, and the usage processing Logic App.

Pitfall: Managed Identity Race Condition

You will likely see this error on first attempt:

BadRequest: The provided principal ID was not found in the AAD tenant(s)

This is a known race condition — the Managed Identity is created but has not yet propagated in Entra ID before the role assignment fires. Re-run azd up without any changes and it will succeed.

Validate the Hub

Once deployed, run:

azd env get-values | grep APIM

You will get your APIM gateway URL. Test it with a chat completion:

$headers = @{
"Content-Type" = "application/json"
"api-key" = "<YOUR_APIM_SUBSCRIPTION_KEY>"
}
$body = '{"messages":[{"role":"user","content":"Hello from the AI Hub Gateway!"}],"max_tokens":100}'
Invoke-RestMethod `
-Uri "https://<your-apim>.azure-api.net/openai/deployments/chat/chat/completions?api-version=2024-02-01" `
-Method POST -Headers $headers -Body $body
PowerShell output showing a successful chat completion response from the Microsoft Foundry Citadel APIM gateway in Azure Sweden Central, with content filter results, prompt filter results, and token usage confirmed.
Validating the Citadel Governance Hub by calling the APIM gateway endpoint via PowerShell, the response confirms gpt-4o-2024-11-20 routing, Content Safety filtering, PII redaction, and token usage tracking are all active.

A successful response with content_filter_results and prompt_filter_results confirms Content Safety and PII redaction are active. Token usage in the response confirms Cosmos DB is logging for cost attribution.

Part 2: Deploying a Citadel Platform Agent Spoke on Azure

The spoke is deployed from the AI Landing Zone Bicep repo. Download it as a ZIP (no GitHub account required):

https://github.com/Azure/bicep-ptn-aiml-landing-zone/archive/refs/heads/main.zip

Extract and navigate to the folder. Create a resource group for the spoke:

az group create --name rg-ai-spoke-dev --location swedencentral

Create a spoke.parameters.json file. Several things to know upfront:

The parameter schema is not the same as the Citadel README suggests. The actual template parameters differ from the example file. Key differences discovered in practice: aiFoundryLocation does not exist as a separate parameter; deployMcp, greenFieldDeployment, deployPostgres, and useCMK are not in this version of the template; and solutionStorageAccountName is simply storageAccountName.

The modelDeploymentList uses nested objects, not flat properties:

"modelDeploymentList": {
"value": [
{
"name": "chat",
"model": { "format": "OpenAI", "name": "gpt-4o", "version": "2024-11-20" },
"sku": { "name": "GlobalStandard", "capacity": 20 },
"canonical_name": "CHAT_DEPLOYMENT_NAME",
"apiVersion": "2025-04-01-preview"
},
{
"name": "text-embedding",
"model": { "format": "OpenAI", "name": "text-embedding-3-large", "version": "1" },
"sku": { "name": "Standard", "capacity": 10 },
"canonical_name": "EMBEDDING_DEPLOYMENT_NAME",
"apiVersion": "2025-04-01-preview"
}
]
}

containerAppsList cannot be an empty array. The template references containerApps[0] internally and will fail validation if the array is empty. Pass at least one placeholder entry.

Deploy:

az deployment group create `
--resource-group rg-ai-spoke-dev `
--template-file main.bicep `
--parameters @spoke.parameters.json

Pitfalls in the Spoke Deployment

AI Search Standard SKU capacity exhaustion. Sweden Central frequently runs out of AI Search Standard SKU capacity. You will see ResourcesForSkuUnavailable. This affects both the standalone Search Service and the AI Foundry Agent Service’s internal Search instance. Disable both:

"deploySearchService": { "value": false },
"deployAAfAgentSvc": { "value": false }

You can re-enable them later once capacity is available, or deploy Search in a different region.

Soft-deleted resources block redeployment. Azure retains soft-deleted Cognitive Services accounts, Key Vaults, and App Configuration stores for up to 90 days. If you delete a resource group and redeploy, the deployment will fail with FlagMustBeSetForRestore or NameUnavailable. Purge them explicitly before redeploying:

# List and purge soft-deleted resources
az keyvault list-deleted --subscription <sub-id> -o table
az keyvault purge --name <name> --location swedencentral
az appconfig list-deleted --subscription <sub-id> -o table
az appconfig purge --name <name> --location swedencentral --yes
az cognitiveservices account list-deleted --subscription <sub-id> -o table
az cognitiveservices account purge --name <name> --location swedencentral

Key Vault purges are slow — allow 2–5 minutes per vault.

Bastion subnet ID resolution fails with networkIsolation=false. When you disable network isolation, the template passes a relative subnet ID to Bastion instead of a fully qualified resource ID. Disable Bastion, Jump VM, and NAT Gateway for the dev spoke:

"deployBastion": { "value": false },
"deployJumpbox": { "value": false },
"deployVM": { "value": false },
"deployNatGateway": { "value": false }

Write parameters files without BOM. On Windows, Out-File -Encoding utf8 adds a Byte Order Mark that causes az deployment to fail with Unable to parse parameter. Use either:

$content | Out-File -FilePath "spoke.parameters.json" -Encoding utf8NoBOM
# or
[System.IO.File]::WriteAllText("spoke.parameters.json", $content, [System.Text.UTF8Encoding]::new($false))

Part 3: Wiring the Citadel Spoke to the Azure APIM Hub

Add the hub’s APIM gateway URL and subscription key to the spoke’s App Configuration:

az appconfig kv set `
--name <spoke-appconfig-name> `
--key "APIM_GATEWAY_URL" `
--label "ai-lz" `
--value "https://<your-apim>.azure-api.net/openai" `
--yes
az appconfig kv set `
--name <spoke-appconfig-name> `
--key "APIM_SUBSCRIPTION_KEY" `
--label "ai-lz" `
--value "<YOUR_APIM_KEY>" `
--yes

Note: az cognitiveservices account connection create with a YAML file for creating an APIM connection in AI Foundry has known bugs in the current CLI version and will throw NoneType or codec errors. Create this connection via the Azure AI Foundry portal UI instead.

Validate End-to-End

$headers = @{
"Content-Type" = "application/json"
"api-key" = "<YOUR_APIM_KEY>"
}
$body = '{"messages":[{"role":"user","content":"Hello from the Citadel spoke!"}],"max_tokens":50}'
Invoke-RestMethod `
-Uri "https://<your-apim>.azure-api.net/openai/deployments/chat/chat/completions?api-version=2024-02-01" `
-Method POST -Headers $headers -Body $body

A successful response with content_filter_results, prompt_filter_results, and usage confirms the full Citadel loop: spoke → APIM gateway → Azure OpenAI → governance telemetry.

PowerShell output showing a successful end-to-end chat completion from the Citadel agent spoke through the Azure APIM Governance Hub, confirming spoke to hub routing, content filter results, and token usage tracking in Sweden Central.
End-to-end validation of the Citadel hub-and-spoke setup: a request from the agent spoke routes through the APIM Governance Hub in Sweden Central, returning a successful gpt-4o response, with Content Safety filtering and token usage tracking confirmed.

What the Microsoft Foundry Citadel Platform Deploys

After following this guide, your rg-ai-hub-gateway-dev resource group contains:

  • APIM gateway with content safety, PII redaction, token rate limiting, and cost attribution policies
  • Azure OpenAI with gpt-4o and text-embedding-3-large
  • Cosmos DB for usage event logging
  • Logic App for usage processing
  • Application Insights for gateway telemetry

Your rg-ai-spoke-dev resource group contains:

  • AI Foundry account and project
  • gpt-4o and text-embedding-3-large deployments
  • Cosmos DB with a conversations container
  • Key Vault, App Configuration, Storage Account, Application Insights, Log Analytics

App Configuration is fully populated with canonical keys (CHAT_DEPLOYMENT_NAME, AI_FOUNDRY_PROJECT_ENDPOINT, COSMOS_DB_ENDPOINT, and more) ready for agent applications to consume.

This Is a Dev Setup — Here Is What Changes for Non-Prod and Production

The configuration above is a starting point, not a production blueprint. Key differences when moving up the environment stack:

APIM SKU. Developer SKU has no SLA and no VNet support. Switch to Premium SKU for non-prod and production. This significantly increases cost and deployment time but enables private networking, multi-region, and availability zones.

Network isolation. For production, set networkIsolation=true and wire the spoke VNet to your hub VNet via peering (hubIntegrationHubVnetResourceId). This requires coordinating private DNS zones across the hub and spoke. The template supports bringing existing DNS zones via the existingPrivateDnsZone* parameters.

AI Search. Re-enable deploySearchService and deployAAfAgentSvc for non-prod and production. If Sweden Central remains capacity-constrained on Standard SKU, deploy Search to a paired region (East US 2 works well) using the searchServiceLocation parameter.

Bastion and Jump VM. For production with networkIsolation=true, re-enable deployBastion and deployJumpbox so operators can access resources inside the private VNet without public endpoints.

Separate parameter files per environment. Maintain spoke.parameters.dev.json, spoke.parameters.nonprod.json, and spoke.parameters.prod.json with environment-specific values. Use a deployment pipeline (GitHub Actions or Azure DevOps) to apply them consistently.

Model versions. Pin specific model versions in parameters files and validate availability in your target region before each deployment. Azure OpenAI model lifecycle moves fast; versions retire on 18-month cycles, and regional availability varies.

Preview Caveats

Citadel is in active development. Several things you should expect to change:

The parameter schemas for both the hub and spoke accelerators will evolve. Parameters discovered missing or renamed in this guide will likely be reorganized again as the repos mature. Always check the actual main.bicep parameter definitions rather than relying on example files.

The az cognitiveservices account connection create CLI command for AI Foundry connections is incomplete at the time of writing. This will improve as the Foundry CLI surface area matures.

The citadel-v1 branch in the AI Hub Gateway repo is flagged as the recommended path for new deployments. By the time you read this, it may have become the default branch with a cleaner deployment experience.

Regional capacity for AI Search Standard SKU fluctuates. Sweden Central is a high-demand region for AI workloads plan for capacity constraints in any SKU beyond Basic for dev scenarios.

Conclusion

Citadel gives you a credible, opinionated starting point for enterprise AI governance on Azure APIM as the AI gateway, AI Foundry as the agent runtime, Cosmos DB for conversation state, and App Configuration as the configuration backbone. Getting it running today requires navigating several rough edges: parameter schema inconsistencies, soft-delete cascades, model version deprecations, regional capacity constraints, and Windows-specific tooling issues.

None of these are blockers. They are the expected friction of working with a platform in active preview. The underlying architecture is sound, and the pieces that do work, APIM governance policies, Content Safety integration, App Config population, and AI Foundry project wiring deliver real value immediately.

If you are building an AI platform for your organization, a Citadel dev setup is a reasonable first step. Treat it as a learning environment to understand the architecture, validate the tooling, and build the parameter files you will need for non-prod and production. Then evolve it deliberately: add network isolation, re-enable Search and Agent Services as capacity allows, and adopt the Citadel contracts (AI Access Contract, AI Publish Contract) to formalize the hub-spoke integration as your agent portfolio grows.

The governance-velocity paradox Citadel sets out to solve is real. Getting the foundation right now, while it is still in preview and the patterns are malleable, is the right time to start.

Final note: This post reflects a hands-on deployment performed in June 2026. Given the pace of change in this space, verify all CLI commands, parameter schemas, and model versions against current documentation before applying them in your own environment.

Azure API Management Load Balancing and Circuit Breaker for AI Backends

Part 5 of 7 in the “APIM for AI Workloads” series

Azure API Management load balancing for AI workloads solves a problem that every team hits once they move beyond a single Azure OpenAI deployment: PTU capacity is finite, PAYG is a safety net, and when things go wrong on one backend, the rest of your workload should not notice. In Part 1 of this series, I described PTU vs. PAYG as a routing problem. This post is where we solve it.

The combination of backend pools, priority-based routing, and circuit breaker rules in APIM gives you a resilient AI gateway that handles three distinct failure modes: PTU saturation (too many tokens consumed against reserved capacity), regional outages, and transient backend errors. None of these requires changes to calling applications. APIM absorbs the complexity and presents a single stable endpoint.

Azure API Management Load Balancing: Backend Pools for AI

APIM’s backend pool feature lets you define a named group of AI backends and route to them as a unit. You reference the pool in the set-backend-service policy by its pool ID. When a request arrives, APIM selects a backend from the pool based on priority and weight, tracks health state via the circuit breaker, and retries on the next available member if the selected backend fails.

For AI workloads, the standard pattern uses two tiers. The first tier is your PTU deployment reserved capacity in a primary region, assigned priority 1. The second tier is a PAYG deployment in a secondary region, assigned priority 2. APIM routes all traffic to the PTU backend as long as the PTU backend is healthy. When PTU returns a 429 (capacity exceeded) error or becomes unreachable, the circuit breaker trips, and APIM automatically fails over to the PAYG backend.

Azure API Management load balancing backend pool with PTU primary PAYG overflow and circuit breaker tripped on unavailable backend
Diagram 1: APIM backend pool with three members. APIM backend pool with three members. The PTU backend (priority 1) handles normal load, while the PAYG backend (priority 2) absorbs overflow. After repeated 429 responses, Backend #3 has tripped its circuit breaker and is bypassed until the probe succeeds.

Priority determines the preference order: lower numbers are preferred. Weight applies when multiple backends share the same priority, distributing load proportionally between them. A common pattern for multi-region PTU deployments is two PTU backends at priority 1, each with a different weight reflecting their provisioned capacity, and a shared PAYG backend at priority 2 as the common overflow.

Circuit Breaker Configuration for Azure API Management AI Backends

The circuit breaker is what makes the backend pool resilient rather than just load-balanced. Without it, APIM continues routing to a saturated or unavailable backend on every request, each one failing with a 429 or timeout before falling back. The circuit breaker short-circuits that path: after a configurable number of failures within a time window, it marks the backend as OPEN and stops sending traffic to it entirely.

Azure API Management circuit breaker state machine showing closed open and half-open states for AI backend failover
Diagram 2: Circuit breaker state machine. CLOSED is normal operation. Exceeding the failure threshold trips the breaker to OPEN, bypassing the backend. After tripDuration seconds, APIM sends a single probe request to test recovery. Success returns to CLOSED; failure reopens the circuit.

The three circuit breaker states map directly to operational behavior:

CLOSED is the normal state. All requests are routed to the backend. Failures APIM counts failures within the configured interval, and the counter resets at the end of each interval if the number of failures remains below the threshold.

After enough failures to exceed the threshold, the breaker trips to OPEN. In this state, APIM bypasses the backend entirely, and APIM routes to the next available pool member without attempting the failed backend again. The tripDuration timer starts counting down immediately.

Once tripDuration elapses, the breaker enters HALF-OPEN and sends a single probe request to test recovery. A successful response transitions the backend back to CLOSED. A failure resets the timer and keeps the circuit OPEN.

For Azure OpenAI specifically, 429 should always be in your failureCondition alongside 503 and 504. A 429 from a PTU endpoint indicates that the provisioned throughput ceiling has been reached and the backend is temporarily unable to serve requests. That is exactly the condition you want to trip the circuit and fail over to PAYG, rather than returning errors to the caller.

Sizing Circuit Breaker Parameters for AI Workloads

The right circuit breaker parameters depend on your traffic pattern and how quickly you need failover to activate. A few practical guidelines:

threshold: For AI workloads, 3 to 5 failures is a reasonable starting point. PTU endpoints return 429 consistently when saturated, so you don’t need a high threshold to detect the condition. Setting it too high means you absorb too many failed requests before failing over.

interval: 60 seconds works well for most workloads. This is the window over which failures are counted. Shorter intervals are more sensitive to transient errors, while longer ones suit bursty traffic patterns where a few failures in a short window are expected.

tripDuration: 30 seconds is a sensible default. PTU capacity refreshes on a per-minute basis, so a 30-second trip duration gives the backend time to recover before the probe fires. For deployments where PTU saturation is a known recurring pattern, a longer trip duration (60 to 120 seconds) reduces the frequency of failed probes.

Retry Policy and Agentic Workload Considerations

Backend pool failover and circuit breaking handle backend-level failures, but you may also want a retry policy in your APIM inbound pipeline for transient errors that do not warrant a full circuit trip. The retry policy can be scoped to specific status codes and configured with a backoff interval, giving you a two-level resilience model: retry for transient errors, circuit break for sustained failures.

For agentic workloads specifically, failover behavior needs careful thought. A conversational agent mid-session that silently switches from a PTU to a PAYG backend will not notice the change at the model API level. But agentic pipelines with multiple sequential tool calls are more sensitive: a mid-pipeline failover can introduce latency spikes that cause timeouts in orchestration layers such as Azure Logic Apps or Semantic Kernel.

The practical mitigation is to expose the remaining token budget via the token limit policy variable from Part 3 and have the orchestration layer monitor it to proactively slow down before circuit breaking kicks in. Prevention is cheaper than recovery when the workload is stateful.

What’s Next in This Azure API Management for AI Series

Part 6 covers semantic caching: how APIM uses an embeddings model and Azure Managed Redis to serve cached responses for semantically similar prompts, reducing token consumption and latency without any changes to calling applications.

New Pricing Plan and Enhanced Networking for Azure Container Apps in Preview

Microsoft recently announced a new pricing plan and enhanced networking for Azure Container Apps in public preview.

Azure Container Apps is a fully managed environment that enables developers to run microservices and containerized applications on a serverless platform. It is flexible and can execute application code packaged in any container without runtime or programming model restrictions.

Earlier Azure Container Apps had a consumption plan featuring a serverless architecture that allows applications to scale in and out on demand. Applications can scale to zero, and users only pay for running apps.

In addition to the consumption plan, Azure Container Apps now supports a dedicated plan, which guarantees single tenancy and specialized compute options, including memory-optimized choices. It runs in the same Azure Container Apps environment as the serverless Consumption plan and is referred to as the Consumption + Dedicated plan structure. This structure is in preview.

Mike Morton, a Senior Program Manager at Microsoft, explains in a Tech Community blog post the benefit of the new plan:

It allows apps or microservice components that may have different resource requirements depending on component purpose or development stack to run in the same Azure Container Apps environment. An Azure Container Apps environment provides an execution, isolation, and observability boundary that allows apps within it to easily call other apps in the environment, as well as provide a single place to view logs from all apps.

At the Azure Container Apps environment scope, compute options are workload profiles. The default workload profile for each environment is a serverless, general-purpose profile available as part of the Consumption plan. For the dedicated workload profile, users can select type and size, deploy multiple apps into the profile, use autoscaling to add and remove nodes and limit the scaling of the profile.

Source: https://techcommunity.microsoft.com/t5/apps-on-azure-blog/azure-container-apps-announces-new-pricing-plan-and-enhanced/ba-p/3790723

With Container Apps, one architect has another compute option in Azure besides App Service and Virtual Machines. Edwin Michiels, a Tech Customer Success Manager at Microsoft, answered in a LinkedIn post the difference between Azure Container Apps and Azure Apps Service, which offer similar capabilities:

In terms of cost, Azure App Service has a pricing model based on the number of instances and resources used, while Azure Container Instances and Azure Kubernetes Service are billed based on the number of containers and nodes used, respectively. For small to medium-sized APIs, Azure App Service may be a more cost-effective option, while for larger or more complex APIs, Azure Container Instances or Azure Kubernetes Service may offer more flexibility and cost savings.

The Consumption + Dedicated plan structure also includes optimized network architecture and security features that offer reduced subnet size requirements with a new /27 minimum, support for Azure Container Apps environments on subnets with locked-down network security groups and user-defined routes (UDR), and support on subnets configured with Azure Firewall or third-party network appliances.

The new pricing plan and enhanced networking for Azure Container Apps are available in the North Central US, North Europe, West Europe, and East US regions. Billing for Consumption and Dedicated plans is detailed on the Azure Container Apps pricing page.

Lastly, the new price plan and network enhancements are discussed and demoed in the latest Azure Container Apps Community Standup.

My Experience with Microsoft Excel During IT Projects

Throughout my extensive career in IT, I often worked with Microsoft Excel. One of my first projects was to leverage Excel to create documentation for a telco’s site surveys. I built a solution with Visual Basic for Applications, a programming language for Excel, and all the other Microsoft Office programs like Word and PowerPoint. With VBA, I could generate multiple worksheets in a Workbook filled with static and dynamic data – from a user’s input or configuration file. Once populated with data and rendered, the Workbook was converted to a Portable Document Format (PDF).

Over the last couple of years, I have had other projects involving Excel. In this post, I will dive into the details of implementations (use cases) concerning Excel Workbooks. One project involved processing Excel files in a Container running on an Azure Kubernetes Service (AKS) cluster, the other generating an Excel Workbook for reporting purposes, orchestrated by an Azure Logic App.

Use Case – Processing an Excel Workbook in a Container

The use case was as follows. In short, I was working on a project for a client a few years ago that required processing a standardized Excel template that their customers could provide for enrichment. The data in the excel file needed to end in a database for further processing (enrichment) so that it could be presented back to them.  The diagram below shows the process of a customer uploading an Excel file via an API. The API would store the Excel in an Azure storage container and trigger code inside a container responsible for processing (parsing the Excel to JSON). The second container had code persist the data in SQL Azure.

Use Case 1

The code snippet (as an example) responsible for processing the Excel file:

For creating the Excel Workbook and its sheet with data, I found the EPPlus library, a spreadsheet library for the .NET framework and .NET core. In the project, I imported the EPPlus NuGet package – specifically, I used the ExcelPackage class.

Now let’s move on to the second use case.

Use Case – Generating an Excel Report in Azure

In a recent project for another customer, I had to generate a report of products inside D365 that needed to be an Excel File (a workbook containing a worksheet with data). The file had to be written to an on-premises file share to allow the target system to consume it. The solution I built was using a Logic App to orchestrate the project of generating the Excel file.

Below you see a diagram visualizing the steps from triggering a package in D365 until the writing of the Excel file in a file share on-premises.

Use Case 2

The steps are:

  1. Logic App triggering a package in D365 (schedule trigger).
  2. Executing the package to retrieve and export data to a SQL Azure Database.
  3. Query by the same Logic App that triggered the package to retrieve the data from the SQL Azure Database.
  4. Passing the data to (the result of the query) to an Azure Function, which will create an Excel Workbook with one sheet containing the data in a given format. The function will write the Excel to an Azure Storage container.
  5. Subsequently, the Logic App will download and write the file to the on-premises file share (leveraging the On-Premises Data Gateway – ODPGW).

The sequence diagram below shows the flow (orchestration) of the process.

Sequence diagram

And below is a part of the Logic App workflow definition resembling the sequence diagram above.

The code snippet (as an example) in the Azure Function responsible for creating the Excel file:

For the creation of the Excel Workbook and sheet with data, I used NPOI – an open-source project which can help you read/write XLS, DOC, and PPT file extensions. In Visual Studio, I imported NPOI NuGet Package. The package covers most of the features of Excel like styling, formatting, data formulas, extracting images, etc. In addition, it does not require the presence of Microsoft Office. Furthermore, I used the StorageAccountClass to write the Excel file.

Conclusion

Microsoft Excel is a popular product available for decades and used by millions of people ranging from businesses heavily relying on Excel to home users for basic administration. Moreover, in IT, Excel is used in many scenarios such as project planning, environment overviews, project member administration, reporting, etc. As said earlier, I have encountered Microsoft Excel various times in my career and built solutions involving the product. The two use-cases are examples of that.

In the first example, I faced a challenge finding a library that supported .NET Core 2.0. I found EPPlus, which did the job for us after experimenting with it first. In the second example, the cost and simplicity were the benefits of using the NPOI library. There were constraints in the project to use solutions with a cost (subscription-based or one-off). Furthermore, the solution proved to be stable enough to generate the report.

Note that the libraries I found are not the only ones available to work with Excel. For instance, SpreadsheetGear, and others, which are listed here. In Logic Apps, you can find connectors that can do the job for you, such as CloudMersive (API you connect to convert, for instance, CSV to Excel).

I do feel with code you have the most flexibility when it comes to dealing with Excel. A standard, of-the-shelve can do the job for you, however, cost (licensing) might be involved or other considerations. What you choose in your scenarios depends on the given context and requirements.