Last verified: September 2026. Foundry's incoming-A2A endpoint is still in public preview; APIM's A2A agent API support is GA.
What you will have at the end
A Foundry agent that any authorised client on your network can call through your API gateway, using the open Agent2Agent (A2A) protocol, like this:
Postman / your app / another agent
│
│ POST https://api.yourcompany.com/agents/helper-agent
│ Header: Ocp-Apim-Subscription-Key
▼
Azure Front Door (optional, if your APIM is private)
▼
Azure API Management ──► gets its own Entra token ──► Microsoft Foundry
(rate limits, keys, (managed identity) (your agent runs,
logging, policies) answers, done)
You'll finish when you send a JSON-RPC message from Postman and get the agent's answer back. Nothing more. Wiring a second agent to call the first, or turning agents into MCP tools, are follow-on articles.
Why bother with the gateway at all?
Foundry agents already have an endpoint. Two reasons to put APIM in front:
- Governance. Subscription keys, per-consumer quotas, content-safety policies, App Insights traces — all in one place you already operate. When Security asks "who can call this agent and how often?", the answer is an APIM product, not a wiki page.
- One front door for everything. The same URL pattern works for Foundry agents today and for agents hosted anywhere else (Container Apps, other clouds) tomorrow.
Before you start — read this once
Things you need
| Thing | Why |
|---|---|
| A Foundry project (New Foundry experience toggled on) | Where the agent lives |
| Owner or Foundry Project Manager on that project | To create agents and assign roles |
| An APIM instance with System-assigned managed identity turned on | APIM must prove who it is to Foundry |
| Contributor on the APIM instance | To add APIs and edit policies |
| Postman (or curl) | To test |
| ~45 minutes | Role assignments take a few minutes to propagate; budget for waiting |
Placeholders used in this article — replace every one of them
| Placeholder | Meaning | Where to find yours |
|---|---|---|
contoso-agents-poc |
Foundry account name (the parent resource) | Foundry portal → Manage → Project details → Parent resource |
contoso-agents-poc |
Foundry project name | Same page → Name. Often identical to the account name for the default project. |
helper-agent |
The agent you're exposing | Whatever you named it |
contoso-apim |
Your APIM instance name | Azure portal |
https://api.yourcompany.com |
The public URL clients use to reach APIM | Either https://contoso-apim.azure-api.net or your custom domain / Front Door hostname |
The one URL that matters
Every Foundry agent has a stable endpoint. Its A2A path is:
https://{account}.services.ai.azure.com/api/projects/{project}/agents/{agent}/endpoint/protocols/a2a
Filled in:
https://contoso-agents-poc.services.ai.azure.com/api/projects/contoso-agents-poc/agents/helper-agent/endpoint/protocols/a2a
Copy that into a notepad. You'll paste it four times.
Two things that will confuse you if nobody warns you
- Foundry's A2A endpoint requires Entra authentication for everything, including reading the agent card. Anything that tries to fetch it anonymously fails. APIM's import wizard does exactly that, so it will fail, and that's expected. We handle it.
- Foundry serves A2A protocol v0.3 by default unless you ask for v1.0 with a header. We'll set that header in APIM so clients don't have to think about it.
Part A — Set up the agent in Foundry
A1. Create the agent (skip if you already have one)
- Open ai.azure.com. Make sure the New Foundry toggle (top right) is on.
- Open your project → Build → Agents → + Create agent.
- Name it
helper-agent. Pick a model (any deployed chat model —gpt-4o-miniis fine). - In Instructions, write something real. A blank agent answers like a generic chatbot, which makes later testing meaningless. Example:
You answer questions about company holiday policy. Be brief. If asked anything else, say it's outside your scope.
- Click Save (top right). This creates version 1. The agent's endpoint is live from this moment — there is no separate "publish" step.
A2. Create the agent card and enable A2A
The agent card is a small JSON document that tells other agents what this one can do. Creating it in the portal also turns on the A2A protocol for the agent.
- Open
helper-agent→ Details tab. - Find the A2A / Agent card section and click Create an agent card (it's marked Preview).
- Fill it in. Write it for a machine that has to decide whether to call you, not for a human:
| Field | What to put | Example |
|---|---|---|
| Name | Pre-filled | helper-agent |
| Description | 1–2 sentences: what it answers, what it doesn't | Answers questions about company holiday and leave policy. Does not handle payroll or IT. |
| Skill name | Short noun phrase | Holiday policy queries |
| Tags | Comma-separated keywords | holiday, leave, policy, hr |
| Skill description | What a request looks like, what comes back | Given a question about leave or holidays, returns the relevant policy answer. |
| Example prompts | 3–4 real ones, one per line | How many days of annual leave do I get? |
- Save.
That's the Foundry side done. Your agent now accepts A2A requests at the URL you copied earlier — from anyone holding a valid Entra token with the right role. Next we make APIM that someone.
Part B — Register the agent in API Management
B1. Open the A2A wizard
- Azure portal → your APIM instance (
contoso-apim) → left menu APIs → + Add API. - Find the tile A2A Agent. Click it.
Don't see the tile? A2A support arrived for v2 tiers first and was extended to classic tiers in the June 2026 release. Check your tier and that the instance is updated. If it's genuinely absent, stop here — the rest of this article won't apply.
B2. Paste the agent card URL (and watch it fail on purpose)
- In URL, paste the card URL — that's your A2A base URL with
/agentCard/v1.0on the end:
https://contoso-agents-poc.services.ai.azure.com/api/projects/contoso-agents-poc/agents/helper-agent/endpoint/protocols/a2a/agentCard/v1.0
- Click Next.
- You'll see a red banner: "We couldn't retrieve the agent card, possibly due to a wrong url or your network configuration. Enter the agent API details manually below."
This is expected. APIM fetched the card anonymously; Foundry said no. The URL is not wrong. Carry on.
B3. Fill the form manually
| Field | Value |
|---|---|
| Protocol | JSON-RPC (pre-selected, greyed out — APIM only supports this one) |
| Runtime URL (JSON-RPC) | Your A2A base URL — the one without /agentCard/v1.0
|
| Agent ID | helper-agent |
| Display name | Helper Agent |
| Name | helper-agent |
| Description | Anything |
| Products | Leave empty for now |
| Base path | agents/helper-agent |
As you type the base path, the two read-only URLs underneath update to:
- Base URL (JSON-RPC):
https://contoso-apim.azure-api.net/agents/helper-agent - Agent card URL:
https://contoso-apim.azure-api.net/agents/helper-agent/agent-card.json
Click Create.
B4. Don't panic about the blank field
The Overview page shows Backend runtime base URL as empty, even though you just filled Runtime URL. Sometimes the Settings page also shows it empty on reload. This is a portal quirk. We will set the backend explicitly in the policy in Part D, which makes this field irrelevant. Move on.
Part C — Let APIM authenticate to Foundry
Foundry only accepts requests carrying an Entra token from an identity that has a Foundry role on the project. APIM will present its own managed identity. Two steps: confirm APIM has one, then grant it a role.
C1. Confirm APIM's managed identity is on
- Azure portal → the APIM service (not the API you just made) → left menu Security → Managed identities → System assigned tab.
- Status should be On. Note the Object (principal) ID — you'll recognise it in the next step.
If it's Off: switching it on is harmless technically, but on a shared or production instance tell whoever owns it first. Then toggle On → Save.
C2. Grant the managed identity a role on the Foundry project
- Azure portal → search for your Foundry project resource (type: Foundry project, not Foundry account) → open it.
- Left menu Access control (IAM) → + Add → Add role assignment.
-
Role tab: search
Foundry User. Select it. Next.
Why Foundry User and not Foundry Agent Consumer? Agent Consumer is the least-privilege role designed for exactly this, but at the time of writing it doesn't appear in the portal's role picker. Foundry User is documented as "or higher" and works. If you need least-privilege, assign Agent Consumer via CLI with role definition ID
eed3b665-ab3a-47b6-8f48-c9382fb1dad6.
-
Members tab: select Managed identity → + Select members.
- Subscription: the one your APIM lives in
- Managed identity dropdown: API Management service
- Pick
contoso-apim→ Select
- Review + assign → Review + assign again.
Back on the IAM page, expand Foundry User and confirm contoso-apim is listed with type Managed identity.
Now wait 3–5 minutes. Role assignments propagate asynchronously. If you test immediately you'll get 401/403 and think something's broken.
Part D — The policy
This is where APIM is told: get a token as yourself, forward everything to the agent's A2A endpoint, and ask for protocol v1.0.
- APIM → APIs →
helper-agent→ left menu Policies. - You'll see an XML editor with
<policies>,<inbound>,<backend>,<outbound>,<on-error>. - Select all, delete, paste this, replacing the URL with yours:
<policies>
<inbound>
<base />
<authentication-managed-identity resource="https://ai.azure.com" />
<set-backend-service base-url="https://contoso-agents-poc.services.ai.azure.com/api/projects/contoso-agents-poc/agents/helper-agent/endpoint/protocols/a2a" />
<set-header name="A2A-Version" exists-action="override">
<value>1.0</value>
</set-header>
</inbound>
<backend>
<base />
</backend>
<outbound>
<base />
</outbound>
<on-error>
<base />
</on-error>
</policies>
- Save.
Line by line, in plain words:
| Line | What it does |
|---|---|
authentication-managed-identity resource="https://ai.azure.com" |
APIM gets an Entra token for itself, scoped to Foundry, and puts it in the Authorization header. This is the whole reason Part C existed. |
set-backend-service base-url=… |
Forward the request to the agent's A2A endpoint. Overrides whatever the Settings page did or didn't save. |
set-header A2A-Version 1.0 |
Ask Foundry for protocol v1.0. Without this you silently get v0.3, which has a different message shape. |
Part E — Get a subscription key
By default an APIM API requires a subscription key. Grab one:
- APIM → left menu Subscriptions.
- Find the row Built-in all-access subscription (or create one scoped to this API).
- Click … on the right → Show/hide keys → copy Primary key.
Keep it in your notepad next to the URL.
Part F — Test it
F1. Which URL to call
- If your APIM is publicly reachable:
https://contoso-apim.azure-api.net - If public access is disabled and you go through Front Door or a custom domain: that hostname instead, e.g.
https://api.yourcompany.com
The path is the same either way: /agents/helper-agent.
F2. Set up the request in Postman
- New request. Method dropdown (left of the URL bar): POST. Yes, POST. GET will return 404 from APIM and you'll waste ten minutes.
- URL:
https://api.yourcompany.com/agents/helper-agent
- Headers tab — two rows:
| Key | Value |
|---|---|
| Ocp-Apim-Subscription-Key | the key from Part E |
| Content-Type | application/json |
- Body tab → raw → set the dropdown on the right to JSON → paste:
{
"jsonrpc": "2.0",
"id": "1",
"method": "SendMessage",
"params": {
"message": {
"messageId": "m1",
"role": "ROLE_USER",
"parts": [
{ "text": "Hello, what can you do?" }
]
}
}
}
- Send.
F3. What success looks like
Status 200 and a body like this:
{
"jsonrpc": "2.0",
"id": "1",
"result": {
"task": {
"id": "resp_…",
"contextId": "ctxt_…",
"status": {
"state": "TASK_STATE_COMPLETED",
"timestamp": "2026-09-06T04:18:09+00:00"
},
"artifacts": [
{
"artifactId": "msg_…",
"parts": [
{ "text": "I answer questions about company holiday policy. …" }
]
}
]
}
}
}
The agent's reply is at result.task.artifacts[0].parts[0].text. That path is what any consumer parses. contextId is the conversation — send it back in later messages to keep context.
You're done. Front Door → APIM → managed identity → Foundry → agent → back. Every hop authenticated, every call logged in APIM.
Troubleshooting — read the status code, it's telling you exactly where it broke
| You see | It means | Do this |
|---|---|---|
404 {"statusCode":404,"message":"Resource not found"}
|
APIM itself replied. Usually wrong HTTP method (GET instead of POST) or wrong path. | Check the method dropdown. Check the base path matches Part B3. |
| 401 "Access denied due to missing subscription key" | APIM replied. No key or wrong header name. | Header must be exactly Ocp-Apim-Subscription-Key. |
401 / 403 with headers like azureml-served-by-cluster or a body mentioning authorization |
Foundry replied — APIM got through, but Foundry rejected APIM's identity. | Role hasn't propagated (wait 5 min) or Part C2 was assigned on the wrong resource (must be the project). Confirm authentication-managed-identity is in the policy. |
405 with allow: POST and azureml-served-by-cluster
|
Foundry replied. You sent a GET to the base path. | This is actually good news: it proves the whole auth chain works. Switch to POST. |
200 but body has "code": -32601
|
Foundry replied: method not found. You're hitting v0.3 but sent a v1.0 body (or vice versa). | Confirm the A2A-Version header line is in the policy and saved. |
| 200 and the agent answers like a generic chatbot | Everything works; your agent has no instructions. | Part A1 step 4. |
| Front Door error page instead of any of the above | Front Door has no route for /agents/*. |
Add a route rule or use the APIM hostname directly if reachable. |
Known gap: the agent card through APIM
GET https://api.yourcompany.com/agents/helper-agent/agent-card.json returns 405 rather than the card.
Because the import wizard couldn't fetch the card (Part B2), APIM never stored one, and it simply proxies the request to the agent's base path — which only accepts POST. Rewriting the path inside the policy (rewrite-uri) didn't take effect on this API type in testing.
Does it matter? Not for calling the agent — you just proved that. It matters for discovery: a client that wants to read the card first (for example, another Foundry agent using "Connect via endpoint") will fail at that step. Two workarounds we haven't fully validated yet: serve the card from the policy using send-request + return-response, or fetch the card once with a user token and host it as a static file. Both are on the follow-up list.
Where to go from here
-
Let another Foundry agent call this one. In the calling agent → Tools → Add → A2A → Connect via endpoint, URL = your APIM URL, auth = custom key
Ocp-Apim-Subscription-Key. Needs the card gap above closed first — or use Connect from Foundry to bypass APIM for Foundry-to-Foundry. - Expose several agents. One A2A API per agent, same policy with the name swapped. After two, script it (Bicep / apiops).
-
Turn agents into MCP tools. A2A APIs can't be exposed as MCP servers directly. Create one plain HTTP API in APIM with one POST operation per agent pointing at each agent's Responses endpoint (
…/endpoint/protocols/openai/responses), then Expose existing API as MCP server. One MCP server, one tool per agent, fully GA end-to-end.
Cheat sheet
A2A base URL https://{account}.services.ai.azure.com/api/projects/{project}/agents/{agent}/endpoint/protocols/a2a
Card URL (v1.0) …/protocols/a2a/agentCard/v1.0
Token scope https://ai.azure.com/.default
Role Foundry User (or Foundry Agent Consumer, ID eed3b665-ab3a-47b6-8f48-c9382fb1dad6)
Version header A2A-Version: 1.0
JSON-RPC method SendMessage (v1.0) | message/send (v0.3)
Reply path result.task.artifacts[0].parts[0].text
Top comments (0)