Deploy Gateway with your MDM

Push Gateway configuration to every employee machine with the MDM you already run, with no Merge client involved

This is the path where your MDM writes each AI client’s own configuration directly. Nothing from Merge runs on the machine. It reaches more clients than the desktop client does, and it works today.

Read Roll out Gateway to your fleet first if you have not chosen a path yet. Come here once you have.

This page is about model traffic. To make the Merge MCP server the only tool source at the same time, see the Agent Handler tab under Devices → Deployment in the dashboard: Claude Code and Codex both accept a managed MCP allowlist your employees cannot override.

1. Decide the key before anything else

Every mechanism below carries the same payload: a base URL, an API key, and a model name. The key is the only one with consequences.

Use a project API key per team, created from the project’s API keys tab. Every request made with it attributes to that project, and the project’s routing policy and budget apply automatically with nothing else to configure. An organization key works everywhere and gives you one undifferentiated bill.

Set the model name to default_routing rather than a pinned model. Model choice then lives in the routing policy, where you can change it once, instead of in a config file on every laptop.

The key is a secret in a world-readable place

A key pushed to a laptop can be read by the person using that laptop. That is unavoidable on every path here, which is why per-team keys matter: a leaked key is scoped to one project’s budget and policy, and can be rotated without touching the rest of the fleet. Never push an organization key that also has access to the Management API.

2. Match the client to a mechanism

What you can do is decided entirely by where each client keeps its settings.

MechanismClientsmacOSWindows
Managed preferencesClaude DesktopConfiguration profileRegistry policy
Environment variablesClaude Code, plus the API key for Zed, OpenCode, Pi, Factory DroidProfile or LaunchAgentMachine environment variables
Config file dropCodex, Zed, Continue.dev, Pi, Factory Droid, OpenCodeScript on a recurring cadenceScript on a recurring cadence
NothingCursorTeam admin console onlyTeam admin console only

Work down that list in order. The first two are enforced by the operating system and survive a reboot untouched. The third is a file in the employee’s home directory that they can edit, so it needs re-assertion.

3. Managed preferences: Claude Desktop

The best case, and worth doing first because it covers Chat, Cowork, and the built-in Claude Code surface in one payload.

1

Configure one machine by hand

On any Mac, open Developer → Configure Third-Party Inference, set the provider to Gateway, and fill in the base URL https://api-gateway.merge.dev/v1/anthropic and your project key. Run Test connection and confirm both checks pass. See the Claude Desktop guide for the full field reference.

2

Export the profile

Use the app’s Export button. It produces a .mobileconfig for macOS or a .reg for Windows carrying exactly what you configured, so there is no hand-authored plist to get wrong.

3

Deploy it unchanged

Upload the .mobileconfig as a configuration profile, or apply the .reg through your Windows policy channel. The keys it contains are:

1{
2 "inferenceProvider": "gateway",
3 "inferenceGatewayBaseUrl": "https://api-gateway.merge.dev/v1/anthropic",
4 "inferenceGatewayApiKey": "mg_your_project_key",
5 "inferenceGatewayAuthScheme": "bearer",
6 "modelDiscoveryEnabled": true
7}

Leave modelDiscoveryEnabled on so the model picker populates itself from Gateway at launch and new models appear without a config change. To pin a curated list instead, set inferenceModels.

Check your restrictions first

Claude Desktop’s connection test sends a real inference request. If your vendor or region restrictions leave no callable route for the model it picks, the test fails with vendor_restrictions_unavailable even though the connection is fine. Either relax the restriction or pin a Model list entry your organization can call, and put it first so it becomes the default.

4. Environment variables: Claude Code

Claude Code takes no config file for routing. It reads the environment, so your MDM sets variables rather than writing a file.

$ANTHROPIC_BASE_URL=https://api-gateway.merge.dev/v1/anthropic
$ANTHROPIC_AUTH_TOKEN=mg_your_project_key
$ANTHROPIC_API_KEY=
$ANTHROPIC_DEFAULT_OPUS_MODEL=default_routing
$ANTHROPIC_DEFAULT_SONNET_MODEL=default_routing
$ANTHROPIC_DEFAULT_HAIKU_MODEL=anthropic/claude-haiku-4-5-20251001

Three things decide whether this works:

  • ANTHROPIC_API_KEY must be empty. Claude Code prefers it over ANTHROPIC_AUTH_TOKEN, so any value inherited from a developer’s old setup silently bypasses Gateway. This is the most common reason a fleet looks configured and reports no traffic.
  • default_routing on the opus and sonnet tiers hands model choice to your routing policy. Leave the haiku tier pinned to a cheap model: Claude Code runs frequent background tasks on it, and routing those adds latency for no benefit.
  • The employee must not be signed in to Anthropic. If they authenticated Claude Code before you deployed this, they run /logout once and relaunch

The same MERGE_GATEWAY_API_KEY variable delivers the credential for Zed, OpenCode, Pi, and Factory Droid, so set it in the same payload even though those clients need a file too.

GUI apps do not inherit your shell

On macOS, an app launched from the Dock never sees a variable exported in ~/.zshrc. Terminal agents inherit it normally; anything with an icon does not. Deliver launchctl setenv through a LaunchAgent that runs at login, or the desktop apps will resolve an empty key and fail to authenticate.

5. Config file drops: everything else

Codex, Zed, Continue.dev, Pi, Factory Droid, and OpenCode each read a file in the employee’s home directory. The exact contents are in each client’s setup guide; this section is about delivering them.

Because these files are user-owned and user-writable, a one-time push is not a control. Deploy them the way your MDM deploys any drifting file: a script that runs on the recurring check-in, checks the current state, and rewrites it if it has changed.

$#!/bin/bash
$# Runs as root on the MDM's recurring cadence. Writes as the console user, since
># these files live in their home directory and must stay owned by them.
>USER_NAME=$(stat -f%Su /dev/console)
>USER_HOME=$(eval echo "~${USER_NAME}")
>TARGET="${USER_HOME}/.codex/config.toml"
>
>read -r -d '' DESIRED <<'CONFIG'
>[model_providers.merge-gateway]
>name = "Merge Gateway"
>base_url = "https://api-gateway.merge.dev/v1/openai"
>env_key = "MERGE_GATEWAY_API_KEY"
>CONFIG
>
># Only write when it differs, so the file's modification time stays meaningful
$# and you can tell real drift from the script running.
$if [ ! -f "$TARGET" ] || ! diff -q <(echo "$DESIRED") "$TARGET" >/dev/null 2>&1; then
$ install -d -o "$USER_NAME" "$(dirname "$TARGET")"
$ echo "$DESIRED" > "$TARGET"
$ chown "$USER_NAME" "$TARGET"
$fi

Treat that as the shape rather than a finished script. It writes one file for one client; a real deployment writes several, and on a multi-user Mac it needs to loop rather than assume the console user.

Your drift window is your check-in interval

Between two runs, an employee can edit the file and route around Gateway. Most MDMs check in every 15 minutes at best, so that is the window. It is a compliance control, not a security boundary. The only enforcement that does not have this gap is blocking the provider endpoints at the network layer.

6. Cursor

Cursor cannot be configured this way at all. Its base URL override and API key are entered in the GUI and held in encrypted local application state, so there is no file to write and no preference domain to set.

Your options:

  • Cursor Business or Enterprise team admin settings, which can restrict models and disable bring-your-own-key
  • Network policy, blocking the provider endpoints Cursor would otherwise reach
  • Point the team at Claude Code or Codex for work that has to be governed

Separately, Cursor’s Agent mode does not accept custom API keys at all, so even a hand-configured Cursor only routes Ask and Plan traffic through Gateway. Tab autocomplete never routes.

7. Per-MDM delivery

The payloads above are the same everywhere; only the delivery differs, and it is the same delivery your MDM already uses for the Workforce desktop client. Follow the mechanics in the matching guide and substitute the payload from this page.

MDMProfiles and registryRecurring scripts
Jamf ProApplication & Custom Settings payloadOngoing policy at recurring check-in
Microsoft IntuneConfiguration profile, or Settings Catalog on WindowsPlatform scripts on macOS, Remediations on Windows
MosyleCustom ProfileCustom Command on a schedule
Workspace ONECustom Settings payloadScripts, scheduled
Group Policy and Configuration ManagerRegistry policyScheduled task or configuration item
Any other MDMAny managed preferences channelAny root script with a recurring cadence

8. Verify

Pushed is not routed. Check in this order, because each one rules out a different failure.

  1. Did the payload land? On a test Mac, defaults read /Library/Managed\ Preferences/<domain>.plist for a profile, or read the file for a script drop
  2. Does the client see it? Claude Code’s /status should show Auth token: ANTHROPIC_AUTH_TOKEN and the Gateway base URL. Claude Desktop’s Test connection runs discovery and a real inference request.
  3. Is traffic arriving? The Gateway dashboard shows requests within a few seconds. This is the only check that proves the whole path, and it needs nothing on the endpoint.
  4. From how many people? Compare distinct users in the dashboard against the size of the group you scoped. Configuration that landed everywhere and reports traffic from four people means it landed and is being bypassed.

Roll out to a canary group and complete all four before widening. A misconfigured ANTHROPIC_API_KEY looks identical to a successful deployment until step 3.

Next steps