An engineering guide to Claude Code on Microsoft Foundry with Azure API Management
Nov 03, 2026
I built CCOF and have taken it live in multiple enterprises. This engineering guide covers architecture, Entra group onboarding, private networking, Claude Code/CLI setup, Desktop configuration and chargeback, using pseudonymised lab evidence.
How do you give developers Claude Code while keeping identity, model access, budgets and private networking under enterprise control? I built Claude Code on Foundry (CCOF) to address that challenge, and I have taken this implementation live in multiple enterprises using this architecture.
This guide explains what developers and administrators get, how the components fit together, and how to deploy and test one user before expanding. CCOF is my implementation, available in my GitHub repository; it is not a Microsoft product.
The screenshots below are from recorded lab runs, not customer production environments. Names and identifiers have been replaced or masked. Enterprise deployment experience does not establish a verified capacity for every workload.
1. What developers and administrators get
| For a developer | For an administrator |
|---|---|
| Claude Code with an organisation-approved model | Onboard an existing Entra security group |
| Microsoft Entra sign-in, without a shared Foundry key | Set models, per-person budgets and token limits |
| Claude Code CLI and the Claude Code VS Code extension | Give each group a cost code and tag people for chargeback |
| Their own CCOF budget through the installed usage skill | View spend by group, developer and model |
| An optional budget status line | Export usage for reporting and use an Azure Monitor workbook |
| A supported, build-checked Claude Desktop configuration on Windows | Keep backends private and reuse an existing APIM where appropriate |
Access comes from group membership. A client configuration file does not grant access by itself.
Figure 1. The group-only console in a live lab. Group identifiers and resource names are replacements; this is not a customer directory.
2. Architecture: one gateway, separate responsibilities
Figure 2. The supplied design: a single-region CCOF stack with zone-redundant APIM and Functions, ZRS storage, and primary/standby Foundry accounts. Cosmos is non-zonal in both regions in this illustration; enabling its zone option is a separate capacity and deployment decision.
| Component | Its job |
|---|---|
| API Management | Validate the caller’s token, enforce admission and token limits, route to the approved Foundry backend |
| Resolver Function App | Read the caller’s entitlement and budget; return a group, exact model permissions and backend ID |
| Sync Function App | Read direct Entra group members and update the Cosmos entitlement projection |
| Reconciler Function App | Read usage on a schedule and write budget state |
| Admin Function App | Group onboarding, configuration and reporting |
| Cosmos DB | Store groups, entitlements, budget state and optional daily chargeback history |
| Log Analytics and Application Insights | Request/accounting telemetry and operational logs |
| Storage | Function packages and optional gateway log archive |
| Microsoft Foundry | Host the Claude deployments |
The resolver is not a developer endpoint. It accepts the gateway’s managed identity. APIM also uses its managed identity to call Foundry, with the account-scoped Cognitive Services User role (the current Azure built-in RBAC role name used by this implementation).
The normal request path is:
- The user signs in to Entra. A helper gives Claude Code a short-lived token for the gateway.
- APIM validates tenant, audience, client application and delegated scope.
- On a cache miss, APIM calls the resolver to determine the authorised group and models.
- APIM applies the configured person/group limits and forwards to Foundry.
- Usage telemetry carries the person, group, model and cost attribution. Background reconciliation updates budgets.
The baseline allow cache is 120 seconds; it is configurable. A person in multiple groups can select a group by its Object ID in x-ccof-project. Without a selector, the configured selection rule chooses the group with the most recorded spend this month, then the lower priority number.
See the architecture and trust boundaries.
3. Prepare the Azure and Entra prerequisites
Before starting, the platform, network and Entra owners agree on:
- The Azure subscription, resource group and regions.
- APIM tier, network access and capacity. A private backend needs a reachable private network path.
- Primary and standby Foundry deployments, exact deployment names and inference geography.
- Cosmos zone capacity where requested, plus throughput for the pilot.
- Network subnet delegation, DNS, routing and outbound access.
- Existing Entra group Object IDs, test members and budgets.
- Who can create Azure role assignments and grant Microsoft Graph application permissions.
For user-only automatic sync, the sync identity uses GroupMember.Read.All; User.Read.All lets it read users’ enabled/disabled state. These are Microsoft Graph application permissions on the Function App’s managed identity, not permissions given to developers. Service-principal membership needs separate permission consideration.
Admin group search and names need their own approved Graph read permissions. New managed-identity grants may take hours to appear in cached tokens. Grant them before the rollout window where possible.
Private IPs belong in DNS, not in the backend URL
Use the account’s hostname:
https://<foundry-account>.services.ai.azure.com/anthropic
Private DNS resolves it to the private endpoint IP. Replacing the hostname with https://10.x.x.x breaks hostname-based TLS validation; CCOF also requires the expected standby hostname.
Private DNS zones are global. Another Foundry region does not need another set of same-named zones. What matters is which zone the APIM network resolves and whether it can reach the returned IP. Peering alone does not provide DNS forwarding.
Figure 3. A cropped Azure portal capture from an earlier East US 2 lab. Resource names and subscription details are replaced. The account, SQL private endpoint and NIC are separate resources; the service itself does not live inside the endpoint subnet.
Figure 4. An earlier lab used public inbound APIM access and enabled outbound VNet integration. A production gateway can instead have private inbound access; that is a separate owner decision, not what this screenshot proves.
If APIM public access is disabled, both the acceptance-test machine and developer machines need the corporate network, VPN or VDI path and private DNS. An operator may deploy through ARM from the internet but still be unable to call the private gateway.
4. Deploy in stages
The deployment-day runbook gives the sequence and expected output. The configuration guide explains each choice.
| Stage | Work |
|---|---|
| 0 | Read-only preflight: subscription, providers, HA support, deployments and existing APIM checks |
| 1 | Create resources and verify their settings |
| 2 | Create the Cosmos database and containers |
| 3 | Assign and verify runtime Azure/Cosmos roles |
| 4 | Create or reuse Entra registrations and groups |
| 5 | Apply app settings, APIM policy/backends and telemetry |
| 6 | Provision or verify the configured private network paths |
| 7 | Publish code, seed data, load membership and export the client configuration |
| 8 | Test authentication, allowed/denied models and accounting |
From a checkout, in PowerShell 7:
$cfg = Join-Path $HOME 'ccof-private/staged.config.json'
$run = Join-Path (Get-Location) 'infra/staged/Invoke-StagedDeployment.ps1'
pwsh -File $run -Stage 0 -ConfigPath $cfg
# Preview, review, then apply one stage at a time.
pwsh -File $run -Stage 1 -ConfigPath $cfg
pwsh -File $run -Stage 1 -ConfigPath $cfg -Apply
Continue through Stage 8 only after the preceding stage succeeds. Keep the configuration and its saved state together. Stage 1 is create-only: decide HA options before it succeeds.
An existing APIM keeps its networking, units and existing APIs. Choose unused CCOF names, for example apiId: ccof-claude-messages, apiPath: ccof and backend ID ccof-foundry. Its global policy still applies, and service-level diagnostics may include its other APIs. Review that scope and cost with the owner.
The runbook budgets a 2–3 hour working window after prerequisites. In the recorded rehearsal, the two-group, priced API goal took about 2 hours 12 minutes; the first real Claude Code reply took about 2 hours 28 minutes. The complete rehearsal and evidence work took longer. Those are lab observations, not an installation-time guarantee.
5. Onboard one Entra group
In the admin console, use Groups → Onboard:
- Find the approved existing group, or enter its Object ID.
- Set the monthly per-person budget and decide whether the group also has a total cap.
- Select the permitted models, backend and limits.
- Review the plan and submit.
Figure 5. Group configuration from the lab. The small USD 1 budget was for testing, not a recommended developer allowance.
A budget, a token quota and provider capacity are different controls. For example, a group-wide quota of two million tokens per day gives only 1,000 tokens per person if 2,000 people share it. Set group limits deliberately rather than copying a demonstration value.
For automatic sync, add the test user to the Entra group. Then check a successful sync run; a timer marked Enabled is not proof of admission. The member data must actually be projected to Cosmos.
6. Configure Claude Code and its CLI
Stage 7 exports a client configuration. Share that file through the organisation’s approved channel, never a user’s token or Azure sign-in cache.
Windows
From the checkout, as the developer:
npm ci --prefix .\client --no-audit --no-fund
$env:CCOF_CONFIG_FILE = (Resolve-Path '<client-config.json>').Path
pwsh -NoProfile -File .\scripts\client\Set-ClaudeCodeGateway.ps1 -ConfigPath $env:CCOF_CONFIG_FILE -Confirmed -UsageStatusLine
& "$env:USERPROFILE\.claude\ccof-token.cmd" login
The last line is for a configuration using Azure CLI sign-in; use the exact sign-in command printed by setup.
macOS
CCOF’s macOS Claude Code setup uses signInMethod: azure-cli. With Azure CLI and PowerShell 7 installed:
npm ci --prefix ./client --no-audit --no-fund
New-Item -ItemType Directory -Force ~/.config/ClaudeGateway | Out-Null
Copy-Item '<client-config.json>' ~/.config/ClaudeGateway/config.json
pwsh -NoProfile -File ./scripts/client/Set-ClaudeCodeGateway.ps1 -Confirmed -UsageStatusLine
& "$HOME/.claude/ccof-token" login
The helper uses its own Azure CLI profile beside the client configuration. It supplies the token to Claude Code without putting it in settings.
The important non-secret settings look like this. Deployment names are examples and must match Foundry exactly:
{
"apiKeyHelper": "'/Users/alex/.claude/ccof-token' token",
"model": "claude-sonnet-5",
"env": {
"ANTHROPIC_BASE_URL": "https://ccof-sample.azure-api.net/ccof",
"ANTHROPIC_MODEL": "claude-sonnet-5",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-5",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4-5",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-opus-5-5",
"CLAUDE_CODE_API_KEY_HELPER_TTL_MS": "240000"
}
}
This excerpt is not the whole setup: the script also handles conflicting provider variables, per-user config, model lists and optional usage integration. It preserves unrelated settings; managed policies remain authoritative.
Figure 6. A real lab CLI request returned OK. The captured build also warned that its local model catalog did not recognise that deployment. Use a client build and model mapping suitable for the deployed models; do not hide compatibility warnings.
In interactive Claude Code, send a small prompt, try an admitted /model choice and inspect the configured connection. The installed CCOF usage skill can report the person’s budget; some Claude Code builds also have a built-in /usage, so verify which command was invoked.
Figure 7. Recorded helper/status output with pseudonymised identifiers. This capture explicitly marks partial pricing and reporting lag. It is not a native Desktop conversation.
Claude Code in VS Code
Setup also configures the extension’s non-secret environment settings and disables its hosted-login prompt for externally managed authentication. Reload the VS Code window and use the Claude Code panel. Confirm the active gateway/model and a request in chargeback; merely opening the extension is not proof of the route.
7. Claude Desktop is a separate configuration
The repository’s Desktop installer supports Windows and checks the installed build’s configuration schema:
pwsh -NoProfile -File .\scripts\client\Set-ClaudeDesktopGateway.ps1 -ConfigPath $env:CCOF_CONFIG_FILE -Confirmed
Depending on the supported build and sign-in method, it selects a compatible helper-script or external-identity-provider profile. It does not bypass managed inference policy. This is different from setting ANTHROPIC_BASE_URL for the CLI.
Figure 8. Configuration example generated from CCOF’s Desktop profile builder, not a screenshot of a successful Desktop chat. The helper-script credential kind is build-dependent; use the setup script’s schema check.
Fully quit Desktop, restart and inspect its supported connection/profile settings. Select the gateway profile, send a small prompt and correlate it with CCOF telemetry. The reusable runbook evidence does not contain a verified successful native Desktop conversation; I am not presenting one here. macOS Desktop setup is not supported by this repository’s installer.
See client configuration and compatibility.
8. Test the user path, not just the deployment
From the user’s machine, on the required network and after sign-in:
pwsh -NoProfile -File ./scripts/client/Test-ClientGatewayAccess.ps1 -Confirmed
This checks the generated token helper, sends a small gateway request and runs the installed Claude Code CLI. Require gateway_result with httpStatus: 200 and claude_code_result with ok: true. A not_installed result is not a Claude Code test.
| Result | First check |
|---|---|
| APIM response says public network access is disabled | VPN/VDI path and private DNS |
| 401 | Sign-in and gateway token audience/scope |
| 403 | Group admission, model permissions or exhausted budget |
| Foundry 429 | Provider quota/capacity or a configured gateway limit |
| Backend failure | Routing, DNS, endpoint health and account-scoped managed-identity role |
After a reconciler run, check the person’s budget. In the admin console, open Budgets → Snapshots → People and Chargeback → Developers.
Figure 9. The lab has two group and two person snapshots. Small amounts round to USD 0.00; an incomplete figure remains marked partial rather than being presented as a complete bill.
Figure 10. Group-level chargeback from the lab. Reports can be filtered by group, developer, model and period.
Usage exports have a 5,000-row cap and report truncation. Split large report requests by the supported filters rather than assuming one CSV includes a 25,000-person population. Names can be resolved for the console with approved Graph permissions; usage exports use object IDs.
The console captures use real lab API data with pseudonymised display values. Their visible demo-editor label comes from a read-only capture’s sign-in configuration substitution; backend authorisation still used an approved Entra token. They are not a demonstration of unauthenticated access.
Is the membership timer healthy?
In the customer’s CCOF Log Analytics workspace, run:
FunctionAppLogs
| where TimeGenerated > ago(1h)
| where Message has "ccof.sync.run.v1"
| extend r = parse_json(substring(Message, indexof(Message, "{")))
| project TimeGenerated, outcome=tostring(r.outcome),
membersAdmitted=tolong(r.counts.membersAdmitted),
writeFailures=tolong(r.counts.writeFailures),
reasons=tostring(r.reasons)
| order by TimeGenerated desc
| take 10
Require a recent succeeded run with the expected member counts and no unexplained write failures. graph_permission_missing needs the Graph grant on the sync identity, not the developer app registration.
9. HA and scaling limits
The diagram protects different failure paths:
- Zone-redundant APIM and Functions protect against a zone failure, with capacity and always-ready compute cost considerations.
- A second Cosmos region helps protect registry availability. Zone settings and service capacity are separate.
- A Foundry priority pool can select a standby when its configured circuit breaker opens.
Same-subscription Foundry accounts share model quota pools. A second account does not add quota. Model matching and standby capacity must be planned; separate subscriptions can provide quota isolation.
APIM v2 and the Function Apps remain a single-region stack. A Central India outage can still stop the service even if Cosmos and the standby Foundry account survive. No diagram should be read as automatic multi-region gateway recovery.
Start with a pilot. Measure successful model requests, realistic streaming, gateway CPU, Cosmos throttling, sync duration and reconciler freshness. Row guards such as maxStates are not throughput guarantees. The sizing guide explicitly separates lab measurements from workload estimates.
Budgets are also not strict invoice caps. Reconciliation is delayed; streamed prompt-cache categories remain unknown to the gateway’s live ledger. A normal reconciler run cannot recreate missing token categories. Treat the displayed spend as a lower bound and reconcile against billing separately.
Runbooks and references
- CCOF repository
- Deployment-day engineering checklist
- Configuration choices
- Developer and fleet onboarding
- Budgets and guard behaviour
- High availability and recovery boundaries
- Claude Code: connecting to an LLM gateway
- Microsoft Learn: APIM token-limit policy
- Microsoft Learn: Claude quotas in Foundry
Deploying CCOF creates billable Azure resources. Use the implementation and runbooks with the organisation’s security, network and capacity reviews; the screenshots show recorded checks, not a certification of every enterprise configuration.
Version 1.0

