Bulk enrollment with your MDM
Push Anyray to your whole fleet with Jamf, Intune, or any MDM.
What you push depends on whether the organization has configured SSO:
| Enrollment mode | Push via MDM | Result |
|---|---|---|
| No SSO configured | One provisioning-token command | A personal ark_… key minted at enrollment — fully zero-touch. |
| SSO configured | The connect binary + an SSO-variant managed-settings file | The first prompt is held for IdP sign-in; then the personal key lands in user scope. |
SCIM and MDM solve different halves of fleet onboarding:
| Mechanism | Source of truth | Credential | Best for |
|---|---|---|---|
| Inbound SCIM | Your IdP owns users, groups, teams, roles, activation, and offboarding. | Static SCIM bearer sent to <gateway>/scim/v2; only its hash is stored. | Identity lifecycle and immediate deactivation of existing keys. |
| MDM provisioning | Your device manager runs enrollment on each endpoint. | Reusable enp_… bootstrap token; each device receives its own ark_… key. | Installing tool config and enrolling managed endpoints or headless machines. |
Use both when appropriate: SCIM owns whether the identity is active, while MDM places the client
configuration on the device. A SCIM deactivation blocks that identity's already-issued key; revoking
an enp_… token only stops future device enrollments.
Enroll an API-key fleet with a provisioning token
A provisioning token (enp_…) is a reusable enrollment credential that lives in your MDM: each machine runs the command once, gets a personal ark_… key, and never needs the token again.
This path is available only before SSO is configured. After SSO is connected, provisioning-token mint, rotation, and redemption are blocked; use the SSO handoff below.
Console (:3000) → Users → Bulk enrollment (MDM). The raw token is shown once — copy it straight into your MDM secrets vault. Tokens accept new enrollments for 365 days; expiry never affects issued keys.
| Option | Description |
|---|---|
| Mode | email — each machine asserts the device owner's email. machine — connect derives a stable machine id instead. |
| Allowed email domains (email mode) | e.g. acme.com, exact match only — sub.acme.com does not satisfy acme.com. |
| Default team (optional) | Team enrollments roll up to. Bound into the key at enrollment — request headers can't override it. Useful for machine-mode tokens. |
Use the origin-qualified enrollment URL shown once when the token is minted;
--yes keeps Connect non-interactive.
Email mode — your MDM substitutes the device owner's email (an empty/unset variable is rejected with email is required for this provisioning token):
- bash
- powershell
curl -fsSL https://app.anyray.ai/connect.sh | sh -s -- --enroll https://app.anyray.ai/enroll/enp_… --user $EMAIL --yes
& ([scriptblock]::Create((irm https://app.anyray.ai/connect.ps1))) "--enroll" "https://app.anyray.ai/enroll/enp_…" "--user" "%LoggedOnUser%" "--yes"
Machine mode — for shared or headless machines. --machine records the user as machine:<id>; set a default team at mint so spend rolls up:
- bash
- powershell
curl -fsSL https://app.anyray.ai/connect.sh | sh -s -- --enroll https://app.anyray.ai/enroll/enp_… --machine --yes
& ([scriptblock]::Create((irm https://app.anyray.ai/connect.ps1))) "--enroll" "https://app.anyray.ai/enroll/enp_…" "--machine" "--yes"
Each machine enrolls, gets a personal ark_… key (backed by a 1-year DevCert that silently re-mints it), and Connect configures every detected tool. In email identity mode that certificate also rolls forward while the machine stays in use, so an active developer is never sent back to re-enroll. In machine identity mode it does not: a machine:… certificate keeps its fixed 1-year lifetime and your MDM re-enrolls the device unattended when it lapses. Keys stay in each user's secret or config store; fleet artifacts and ACP registries carry no static key. Enrolled users appear in Users; the user detail shows Source: MDM provisioning.
Route the supported fleet surfaces
Run the enrollment command in the signed-in user's context so Connect can write per-user config. Use the managed artifacts below where a vendor exposes a machine policy.
The values emitted by anyray-connect managed bootstrap enrollment and older clients. Enrolled
clients treat /connect/policy as the live source for gateway routing origin, team attribution from
the bound personal key, enabled tools, and shared skills. sync and the detached refresh worker
repair drift automatically. Tool reconciliation is enable-only: an omitted id is not reverted or
removed from the local managed-tool record.
| Surface | Push | Boundary |
|---|---|---|
| Claude Code | Managed settings for API-key fleets; deferred SSO for subscription fleets. | Routing is automatic after each user enrolls. |
| Codex app / CLI / IDE | Enroll as the signed-in user. For managed API-key auth, deploy the helper below and emit desktop codex --mobileconfig --helper-path <absolute>. | API-key and ChatGPT-seat routing are automatic. If the model picker omits custom models, set ANYRAY_CODEX_MODEL and rerun Connect. |
| Claude Desktop | Deploy the helper, then emit desktop claude --mobileconfig --helper-path <absolute> on macOS or desktop claude --reg --helper-path <absolute> on Windows. | Third-party inference is API-billed. Use Claude Code for a first-party Claude subscription. |
| JetBrains | On IDE 2025.3.2+ with JetBrains AI 253.30387.147+, Connect adds Claude and Codex ACP agents. For a fleet push, emit the acp.json artifact below instead of running Connect per user. | ACP traffic routes automatically; native JetBrains AI and Copilot traffic does not. |
| Devin Desktop | Connect adds the ACP agents to Devin's registry. | Enable them once in Devin and restart. Native usage is visible only through the Devin connector. |
| VS Code Copilot Chat | Connect installs the Anyray provider and stores the user key in VS Code SecretStorage. | Select an Anyray · … model. Native traffic stays on GitHub; its connector requires Business or Enterprise. |
| Cursor | Native Team Claude/Opus is the default: run anyray-connect <gateway> --tools cursor --user <email> --yes, or use anyray-connect --sso <tenant-link> --tools cursor --yes for Anyray SSO enrollment. Cursor Team SSO remains untouched. Restart once for MCP. If Connect must restore an earlier BYOK override, fully quit Cursor when prompted. | No provider key is added. Local Shell/MCP hooks optimize supported context; native inference stays on Cursor and has no gateway trace. The separate OpenAI BYOK mode is API-billed and requires explicit --org. |
A managed third-party-inference policy puts Claude Desktop into its separate Claude-3p profile, so
the app opens signed out with an empty conversation list on every device you push it to. Nothing is
deleted — conversations belong to the user's Claude account and stay available at claude.ai — but
without a heads-up this reliably arrives as "the rollout wiped my chats." Users cannot undo a managed
policy themselves; removing it is an MDM action.
JetBrains and Devin ACP need Node (npm/npx); no WSL support; JetBrains versions below the floor are left unchanged. For Remote Development, run Connect as the remote backend user. The VS Code extension installs into every profile registered at apply time; a profile created later needs a re-run or Apply Extension to all Profiles.
For Devin Enterprise and GitHub Copilot Business/Enterprise reporting, configure
subscription spend connectors once on the gateway. Cursor's
Team pricing page advertises Admin API usage statistics, but its API overview marks the Admin API
Enterprise-only. Verify an admin:* key can call /teams/filtered-usage-events before promising
native Cursor reporting to a Team-plan customer. Official admin APIs only — device networking
never changes. Copilot Individual isn't covered.
Package the desktop credential helper (Claude Desktop & Codex)
Claude Desktop and Codex fleet policies call a local credential helper as the signed-in user. Artifact generation requires an explicit --helper-path.
/usr/local/bin/anyray-connect (macOS/Linux) or C:\Program Files\Anyray\anyray-connect.exe (Windows). Never an npm/npx cache, temp dir, network share, or an admin's home directory.--write verifies the binary and replaces only an Anyray-owned helper — it refuses another file. For MDM packaging use --print: helper bytes on stdout, notes on stderr.--helper-path on every Claude/Codex artifact. Activate the policy only after binary and wrapper are in place.sudo /usr/local/bin/anyray-connect desktop helper --write --platform posix \
--bin /usr/local/bin/anyray-connect
& "C:\Program Files\Anyray\anyray-connect.exe" desktop helper --write `
--platform windows --bin "C:\Program Files\Anyray\anyray-connect.exe"
anyray-connect desktop --mobileconfig claude.mobileconfig \
--gateway https://gw.acme.com \
--helper-path /usr/local/bin/anyray-credential-helper
anyray-connect desktop --reg claude.reg \
--gateway https://gw.acme.com \
--helper-path "C:\Program Files\Anyray\anyray-credential-helper.cmd"
anyray-connect desktop codex --mobileconfig codex.mobileconfig \
--gateway https://gw.acme.com \
--helper-path /usr/local/bin/anyray-credential-helper
For a Windows Codex managed_config.toml, omit --mobileconfig and pass the .cmd helper path — Connect invokes it via cmd.exe, as Codex requires.
The wrapper holds no key — it runs the chosen absolute binary and reads the signed-in user's key. An unenrolled user gets none.
JetBrains and shell fleet artifacts
Credentialless managed --print artifacts — explicit flags only, stdout is the artifact:
anyray-connect managed --print --tool jetbrains \
--bin /usr/local/bin/anyray-connect --npx /usr/local/bin/npx > acp.json
Entries launch Connect's ACP agents at the fleet-stable paths; gateway, attribution, and the personal key resolve from each user's own enrollment at agent launch — the file carries none of them. An MDM file drop replaces the whole file; merge the agent_servers object in your config tool if developers add their own agents.
anyray-connect managed --print --tool shell --gateway https://gw.acme.com > anyray.sh
The snippet exports the two gateway base URLs and nothing else — a shared file never carries a credential or shadows a seat sign-in.
Zero-touch enrollment for ACP seats and shell (bootstrap file + loopback daemon)
A developer who never ran Connect still needs one enrollment. Deploy the fleet bootstrap file; the ACP launcher starts it for them:
anyray-connect managed --print --tool bootstrap \
--sso https://app.anyray.ai/sso/tnt_acme --gateway https://gw.acme.com \
--bin /usr/local/bin/anyray-connect > bootstrap.json
--bin (optional) records where your MDM installed Connect, for trigger surfaces that aren't the binary itself — today the VS Code extension uses it.
Surfaces spawn the bin binary, so deploy the bootstrap file admin/root-owned, never user-writable. On Windows, --bin must point under Program Files (enforced at artifact generation), and %ProgramData% itself lets standard users create files — have your MDM create %ProgramData%\Anyray\bootstrap.json as SYSTEM/admin at provisioning time, before first login. An invalid bin value is ignored; sign-in nudges still work.
Deploy it to the fixed system path — /etc/anyray/bootstrap.json (Linux), /Library/Application Support/Anyray/bootstrap.json (macOS), %ProgramData%\Anyray\bootstrap.json (Windows). Four surfaces read it: the ACP launcher (JetBrains / Devin), the prompt hook (Claude Code / Cursor), the key-refresh daemon (below), and the VS Code extension (push the extension via your fleet's VS Code extension management). Each shows a sign-in message with a verification URL and code on first unenrolled use — prompts are never blocked or altered; sign-in completes in the background and the next use works. The file is configuration, not credential — world-readable is fine.
For shell / SDK traffic, deploy the loopback daemon trio:
anyray-connect managed --print --tool proxy \
--bin /usr/local/bin/anyray-connect --platform launchd > ai.anyray.connect.fallback.plist
anyray-connect managed --print --tool shell --loopback > anyray.sh
# plus the bootstrap file from above
Deploy the plist to /Library/LaunchAgents/ (macOS) or the systemd unit (--platform systemd) to /etc/systemd/user/ + systemctl --global enable anyray-fallback.service (Linux). The snippet points shell/SDK base URLs at the daemon's fixed loopback port; the daemon reads the real gateway from the bootstrap file until the user enrolls. On the first keyless rejected request it starts device-grant sign-in and raises a desktop notification with the URL and code. Requests fail visibly until sign-in completes; afterwards the daemon completes credentialless requests with the user's own key. A request that presents its own credential is never touched.
Revoke and rotate
Revoking a provisioning token stops new enrollments only — it offboards no one. Enrolled machines hold a 1-year DevCert that silently re-mints keys, and actively-used keys slide-renew. In email identity mode that certificate also rolls forward while the machine is in use, so waiting it out is not an offboarding plan at all. To offboard a developer:
- Revoke the user — Users → Offboard a developer, or post
/admin/revoked-users. Blocks every re-mint; propagates in ~15 min. - Revoke the live key — delete
/admin/client-keys/:idfor an immediate cutoff.
Step 1 takes an email, so it applies to email identity mode. A device enrolled in machine mode is bound to machine:…, which the revocation list cannot hold — cut it off with step 2 (revoke its live key), and revoke the provisioning token so it cannot re-enroll. Its certificate is not rolled forward, so it also lapses at its fixed 1-year expiry.
To rotate an exposed token: revoke it on the Users page (new enrollments stop immediately), mint a fresh one with the same config (new enp_… id), and push the updated command. Already-enrolled devices are unaffected.
Pin Claude Code routing with managed settings
A user's ~/.claude/settings.json can be edited or wiped. Claude Code also reads a system-path managed-settings.json that ranks above user and project settings — your MDM (Jamf, Intune, Ansible) can pin gateway routing fleet-wide.
anyray-connect managed emits or installs that file with no credential:
- Subscription (seat) fleet — a standalone env file is intentionally rejected. Each seat keeps its own OAuth sign-in, but the gateway also requires a per-user verified key. Use the zero-touch SSO hooks-only artifact instead.
- API-key fleet — the file owns routing + headers; per-user
ark_keys come from provisioning-token enrollment above and stay in user-scope settings.
Where the file lives
| OS | Path |
|---|---|
| macOS | /Library/Application Support/ClaudeCode/managed-settings.json |
| Linux / WSL | /etc/claude-code/managed-settings.json |
| Windows | C:\Program Files\ClaudeCode\managed-settings.json |
Claude Code before v2.1.75 uses the legacy Windows path C:\ProgramData\ClaudeCode\managed-settings.json.
Package and deploy
managed --print writes JSON to stdout for a Jamf profile, Intune policy, or Ansible template; notes stay on stderr. Add --out <path> to write the file directly instead — required when you package from PowerShell (see below). Prefer --team over --user — a static header can't vary per machine. --intent, --intent-label, and --session are also accepted.
anyray-connect managed --print --gateway https://gw.acme.com --team eng > managed-settings.json
Subscription fleet: skip this artifact and package the deferred-SSO one below — hooks only; enrollment writes the seat route and personal key together in user scope.
On a single host, sudo anyray-connect managed --write … merges into any existing file (non-Anyray keys preserved, original backed up); managed --revert removes only the Anyray-owned keys. The file loads at Claude Code start — restart open sessions.
Whether its env block overrides an exported shell variable (e.g. ANTHROPIC_BASE_URL in ~/.zshrc) is undocumented by Claude Code — verify routing on one machine before a fleet rollout.
Zero-touch SSO for subscription (seat) fleets
Seats bring their own OAuth, but the gateway still requires a verified per-user key. Deferred SSO closes the gap: your MDM deploys everything; each developer signs in through your IdP once, in-tool, on first use.
Push two artifacts — no credential in either:
e.g. /usr/local/bin/anyray-connect on macOS / Linux, C:\Program Files\Anyray\anyray-connect.exe on Windows — the managed file references this path; update the binary in place.
Deployed to the managed-settings path. --bin is where your
MDM installs the binary on the managed machines, not on the one you package from:
- bash
- powershell
curl -fsSL https://app.anyray.ai/connect.sh | sh -s -- managed --print --sso https://app.anyray.ai/sso/<tenantId> --bin /usr/local/bin/anyray-connect --out managed-settings.json
& ([scriptblock]::Create((irm https://app.anyray.ai/connect.ps1))) "managed" "--print" "--sso" "https://app.anyray.ai/sso/<tenantId>" "--bin" "C:\Program Files\Anyray\anyray-connect.exe" "--out" "managed-settings.json"
--out, not >, from PowerShell--out has Connect write the file itself, UTF-8 with no BOM. A redirect there
produces a file no JSON reader accepts: Windows PowerShell 5.1 — the default
shell on a managed Windows box — writes UTF-16LE for >, and -Encoding utf8
adds a BOM. The installer also ends by propagating Connect's exit code, which
unwinds out of the scriptblock and closes the window, so a form that captures
the output and writes it on a second statement never writes anything.
The SSO variant is hooks-only, no env — the per-user key lands in user scope at enrollment. The first prompt is held with a sign-in URL and code; nothing reaches a provider until IdP sign-in completes. Keys auto-renew while in use.
Connect your IdP first (Users → Single sign-on) — otherwise every prompt is held with an "ask your admin" notice. Headless/CI machines can't browser-sign-in; use a service key instead.