# Foundry Cross Resource

> >

- **Type:** Skill
- **Install:** `agentstack add skill-aiappsgbb-awesome-gbb-foundry-cross-resource`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [aiappsgbb](https://agentstack.voostack.com/s/aiappsgbb)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [aiappsgbb](https://github.com/aiappsgbb)
- **Source:** https://github.com/aiappsgbb/awesome-gbb/tree/main/skills/foundry-cross-resource

## Install

```sh
agentstack add skill-aiappsgbb-awesome-gbb-foundry-cross-resource
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Cross-Resource Model Invocation via Foundry AI Gateway

> **Status — verified live on 2026-04-23** with Foundry account
> `xtest-foundry-mr5kfi` / project `xtest-proj-mr5kfi` (Sweden Central) calling
> deployment `gpt-4o-mini v2024-07-18` hosted on a different Azure OpenAI
> account (`acme-aoai-shared`) through APIM `acme-ai-apim`. Both **ApiKey**
> and **ProjectManagedIdentity** auth paths returned `PONG` on all three
> invocation patterns. See "Verified working configuration" at the end.
>
> **Default in worked examples below: `gpt-5.4-mini`** (current
> Foundry-routable chat-mini family, May 2026). The original verification
> was on `gpt-4o-mini`; the gateway routes by `deployment-name-in-path`
> regardless of model family, so the recipe is mechanically identical for
> any deployment your APIM backend actually carries. The exact API
> `version` string in the connection metadata below is illustrative —
> use whatever your backend deployment is registered with (check
> `az cognitiveservices account deployment show`).

---

## 1. What this skill solves

You have a Foundry project ("**consumer**") that needs to invoke models
deployed on a **different** Azure OpenAI / AI Services account ("**backend**"),
fronted by an Azure API Management instance acting as the AI Gateway. You want
to address those models via the Foundry-native string
`connectionName/deploymentName` so the application code is identical to a
local-deployment call.

```
┌────────────────────────┐                ┌──────────────────────────┐                ┌─────────────────────────────┐
│  Consumer Foundry      │                │  APIM AI Gateway         │                │  Backend AI/OpenAI account  │
│  project               │  Foundry MI    │  acme-ai-apim        │   APIM MI →    │  acme-aoai-shared       │
│  xtest-proj-mr5kfi     ├───or────────► │   /xtest-aoai            ├──Bearer───────►│  gpt-5.4-mini deployment    │
│  (Sweden Central)      │  ApiKey        │   /xtest-aoai-pmi        │  (msi token)   │  (East US 2)                │
│                        │                │                          │                │                             │
│  ApiManagement         │                │  validate token /        │                │  Cognitive Services User RBAC│
│  connection            │                │  enforce subscription    │                │  granted to APIM MI         │
│  category=ApiManagement│                │  set-backend-service     │                │                             │
│  target=APIM API URL   │                │  authentication-managed- │                │                             │
│  authType=ApiKey | PMI │                │   identity → backend     │                │                             │
└────────────────────────┘                └──────────────────────────┘                └─────────────────────────────┘
```

The consumer project never holds the backend's API key or RBAC. APIM is the
trust boundary.

---

## 2. The 100% reliable checklist

Before any agent call, ALL of these must be true. Cross them off in order;
the official validator script
[`test_apim_connection.py`](https://github.com/microsoft-foundry/foundry-samples/blob/main/infrastructure/infrastructure-setup-bicep/01-connections/apim/test_apim_connection.py)
plus the field-tested probes in §8 below verify each step end-to-end.

| # | Item | How to verify |
|---|------|---------------|
| 1 | Backend AI Services / Azure OpenAI account exists with the model deployed | `az cognitiveservices account deployment list -g  -n ` |
| 2 | APIM exists (any SKU; Developer is fine for testing, Standard v2/Premium for prod) and has system-assigned managed identity enabled | `az apim show -g  -n  --query identity` |
| 3 | APIM MI has `Cognitive Services User` (or `Cognitive Services OpenAI User`) on the backend account | `az role assignment list --assignee  --scope ` |
| 4 | APIM has an API whose path you'll target (e.g., `/xtest-aoai`) with **inbound policy that calls `set-backend-service` + `authentication-managed-identity` + Bearer header injection** (see §6) | `Invoke-RestMethod -Uri https://.azure-api.net//deployments//chat/completions?api-version=2024-10-21 -Headers @{api-key='';...}` returns 200 |
| 5 | (ApiKey only) An APIM subscription scoped to that API (or to a product the API is in) exists; you have the primary key | `az apim subscription show ...` |
| 6 | (PMI only) APIM API has `subscriptionRequired: false` AND inbound policy includes `` with `` containing the **consumer project MI's client/app ID** | API GET shows `subscriptionRequired:false`; policy GET shows the GUID |
| 7 | Consumer Foundry project exists, has Azure AI User RBAC for the caller, and (PMI) has system-assigned managed identity enabled | `az cognitiveservices account project show ... --query identity` |
| 8 | An `ApiManagement` connection exists on the consumer project (NOT `AzureOpenAI`, NOT `AIServices`) | See §5 for the verified PUT body |
| 9 | The connection's `metadata.models` is a **JSON-stringified** array (NOT a real array) and `metadata.deploymentInPath` matches the backend style | See §5 — this is the most common silent error |
| 10 | Caller code uses `model="/"` and calls `responses.create(...)` (NOT `chat.completions.create`) | See §7 |

If step 4 doesn't return 200 outside Foundry, no Foundry call will work. Always
smoke-test the gateway directly first.

---

## 3. Decision tree

```
                          ┌──────────────────────────────────────┐
                          │ What backend does APIM forward to?   │
                          └─────────────┬────────────────────────┘
                                        │
              ┌─────────────────────────┴────────────────────────┐
              │                                                  │
   AOAI / AI Services on /openai                       OpenAI v1 (/v1/...)  
   (most common — Azure OpenAI)                        or Anthropic etc.    
              │                                                  │
              ▼                                                  ▼
   metadata.deploymentInPath = "true"           metadata.deploymentInPath = "false"
   metadata.inferenceAPIVersion = "2024-10-21"  metadata.inferenceAPIVersion = ""  (or omit)
   models[].properties.model.format = "OpenAI"  models[].properties.model.format = "OpenAI"|"Anthropic"|"NonOpenAI"
                                                modelDiscovery.listModelsEndpoint = "/models"
                                                modelDiscovery.deploymentProvider = "AzureOpenAI"|"OpenAI"|"Anthropic"|"NonOpenAI"

                          ┌──────────────────────────────────────┐
                          │ How do callers prove identity to APIM?│
                          └─────────────┬────────────────────────┘
                                        │
              ┌─────────────────────────┼────────────────────────┐
              │                         │                        │
        APIM subscription key   Project Managed Identity    Both (dual-auth)
              │                  (no static secrets)               │
              ▼                         ▼                        ▼
   authType: "ApiKey"           authType: "ProjectManagedIdentity"  Two connections OR
   credentials.key:        credentials: {}                     APIM 
   APIM api: subscription req   audience:                           policy that branches
   APIM policy: pass-through      "https://cognitiveservices.azure.com"
                                APIM policy: validate-azure-ad-token
                                  with 
                                  containing project MI clientId
                                APIM api: subscriptionRequired=false
```

---

## 4. APIM-side configuration

### 4.1 Service URL on the API

The Foundry connection target is `https://.azure-api.net/`.
Inside the API's inbound policy you `set-backend-service` to the backend
account. Foundry then appends `/deployments/{dep}/chat/completions?api-version=...`
(or `/v1/responses` etc.) to the gateway URL.

Operations on the API (i.e., the routes Foundry will call) must therefore
match the AOAI / OpenAI surface. The simplest setup is **catch-all** with one
operation per HTTP verb on the wildcard path `/{*path}`. For Azure OpenAI
backends, all of the following must reach the inbound policy and 200:

```
POST /deployments/{deployment-id}/chat/completions?api-version=2024-10-21
POST /deployments/{deployment-id}/embeddings?api-version=2024-10-21
POST /v1/responses?api-version=preview          (Responses API)
GET  /models?api-version=2024-10-21             (model catalogue, used for dynamic discovery)
```

### 4.2 ApiKey-only inbound policy (verified working — `xtest-aoai`)

```xml

    
        
        
        
        
            @("Bearer " + (string)context.Variables["msi-access-token"])
        
    
    
    
    

```

Subscription enforcement is at the API/product level (set
`subscriptionRequired: true` and configure `subscriptionKeyParameterNames.header`
to `api-key` so the caller sends `api-key: `, not
`Ocp-Apim-Subscription-Key`). Foundry sends the `api-key` header.

### 4.3 PMI-only inbound policy (verified working — `xtest-aoai-pmi`)

```xml

    
        
        "
            header-name="Authorization"
            failed-validation-httpcode="401"
            failed-validation-error-message="Unauthorized: token did not match expected audience or application">
            
                
            
            
                https://cognitiveservices.azure.com
                https://cognitiveservices.azure.com/
            
        
        
        
        
            @("Bearer " + (string)context.Variables["msi-access-token"])
        
    
    
    
    

```

PMI API also needs `subscriptionRequired: false` on the API resource
(otherwise APIM rejects with 401 before the policy runs).

> **Why `` and not `` with `xms_mirid`?**
> Both mechanisms work in principle, but the `xms_mirid` value Foundry's
> project MI puts in its token is **not** the project ARM ID — it varies by
> resource provider and current APIs.  The MI's `appid` claim is universally
> present and stable, so `` is the documented and
> most reliable check. (Verified 2026-04-23: a policy requiring
> `xms_mirid = ` returned 401; switching to `` returned 200.)

### 4.4 Dual-auth inbound policy (ApiKey OR PMI)

```xml

    
        
        
            
                "
                    header-name="Authorization"
                    failed-validation-httpcode="401">
                    
                        
                    
                    
                        https://cognitiveservices.azure.com
                        https://cognitiveservices.azure.com/
                    
                
            
        
        .openai.azure.com/openai" />
        
        
            @("Bearer " + (string)context.Variables["msi-access-token"])
        
    

```

API must have `subscriptionRequired: false` (so PMI calls aren't rejected up
front); the `` branch enforces token validation only when no
subscription was used.

---

## 5. Connection schema (verified live)

ARM type:
`Microsoft.CognitiveServices/accounts/projects/connections@2025-04-01-preview`.
Reachable also via the Foundry data-plane endpoint
`https://.services.ai.azure.com/api/projects//connections/?api-version=v1`.

> ⚠ **The `metadata.models` and `metadata.modelDiscovery` fields are
> JSON-encoded strings, not real JSON objects.** This is because Azure
> connection metadata is a flat string→string dictionary. Pass the JSON as a
> `string` value or your PUT will be silently ignored / rejected.

### 5.1 ApiKey connection — verified working PUT body

```json
{
  "properties": {
    "category": "ApiManagement",
    "target": "https://acme-ai-apim.azure-api.net/xtest-aoai",
    "authType": "ApiKey",
    "credentials": { "key": "" },
    "isSharedToAll": true,
    "metadata": {
      "deploymentInPath": "true",
      "inferenceAPIVersion": "2024-10-21",
      "models": "[{\"name\":\"gpt-5.4-mini\",\"properties\":{\"model\":{\"name\":\"gpt-5.4-mini\",\"format\":\"OpenAI\",\"version\":\"2026-04-30\",\"publisher\":\"Microsoft\"}}}]"
    }
  }
}
```

PUT URL:

```
https://management.azure.com/subscriptions//resourceGroups//providers/
Microsoft.CognitiveServices/accounts//projects//
connections/?api-version=2025-04-01-preview
```

Or via the data plane:

```
PUT https://.services.ai.azure.com/api/projects//connections/?api-version=v1
```

### 5.2 PMI connection — verified working PUT body

```json
{
  "properties": {
    "category": "ApiManagement",
    "target": "https://acme-ai-apim.azure-api.net/xtest-aoai-pmi",
    "authType": "ProjectManagedIdentity",
    "credentials": {},
    "audience": "https://cognitiveservices.azure.com",
    "isSharedToAll": true,
    "metadata": {
      "deploymentInPath": "true",
      "inferenceAPIVersion": "2024-10-21",
      "models": "[{\"name\":\"gpt-5.4-mini\",\"properties\":{\"model\":{\"name\":\"gpt-5.4-mini\",\"format\":\"OpenAI\",\"version\":\"2026-04-30\",\"publisher\":\"Microsoft\"}}}]"
    }
  }
}
```

`audience` lives at `properties.audience`, NOT in metadata. The data-plane
GET strips it from the response, but it is required on PUT and is what Foundry
uses when requesting the MI token.

### 5.3 Metadata reference (full)

| Field | Type | Required | Default | Purpose |
|-------|------|----------|---------|---------|
| `deploymentInPath` | string `"true"`/`"false"` | Yes (in practice — without it: `Upstream gateway returned NotFound`) | none | `"true"` → AOAI shape `/deployments/{dep}/chat/completions`. `"false"` → OpenAI v1 shape with model in body. |
| `inferenceAPIVersion` | string | Recommended for AOAI | none | Appended as `?api-version=...`. Use `2024-10-21` (or newer GA) for AOAI; leave empty for `/v1/responses` style backends. |
| `deploymentAPIVersion` | string | Optional | `inferenceAPIVersion` | Used for `modelDiscovery` list calls. |
| `models` | **string** (JSON array, escaped) | Required if no `modelDiscovery` | none | Static catalogue. Must be JSON-stringified. |
| `modelDiscovery` | **string** (JSON object, escaped) | Required if no `models` | none | Dynamic discovery. Must be JSON-stringified. Defaults: `listModelsEndpoint=/deployments`, `getModelEndpoint=/deployments/{deploymentName}`, `deploymentProvider=AzureOpenAI`. Override to `/models` and `OpenAI` for OpenAI-v1 style backends. |
| `customHeaders` | string (JSON object) | Optional | none | Adds extra HTTP headers on every Foundry-to-APIM call. |
| `authConfig` | string (JSON object) | Optional | none | For non-`Authorization`-header auth (e.g., gateway expects `x-api-key: Bearer `). |

`models[].properties.model.format` accepts `OpenAI`, `Anthropic`, `NonOpenAI`.
For Azure OpenAI deployments use `OpenAI`.

### 5.4 What does NOT work (verified failures)

| Connection variant | Result |
|--------------------|--------|
| Connection with **no** `metadata` at all | `400 Model gateway error: Upstream gateway returned NotFound` |
| Connection with `metadata.modelDiscovery` (stringified) but **no** `models` | `400 Model gateway error: Upstream gateway returned NotFound` (against AOAI backends — AOAI's `/deployments` data-plane endpoint isn't a list) |
| `models` passed as a real JSON array (not stringified) | Foundry stores it but inference returns `Upstream gateway returned NotFound`; this is a silent footgun |
| `authType: "AAD"` (legacy alias) on **Responses API** | Inconsistent — sometimes accepted, sometimes rejected. Use `ProjectManagedIdentity` for Responses API / hosted agents. |
| `authType: "ProjectManagedIdentity"` on **Assistants v1 API** | **Rejected** — `"AuthType ProjectManagedIdentity for connection  is not supported"`. The standard-agent Assistants API (`/assistants`, `/threads//runs`) only accepts `authType: "AAD"`. |
| `authType: "AAD"` on **private VNet standard ag

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [aiappsgbb](https://github.com/aiappsgbb)
- **Source:** [aiappsgbb/awesome-gbb](https://github.com/aiappsgbb/awesome-gbb)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-aiappsgbb-awesome-gbb-foundry-cross-resource
- Seller: https://agentstack.voostack.com/s/aiappsgbb
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
