Skip to content

Give agents API access without giving them the keys

Your agents call your APIs. The keys stay with exe.dev, outside the VM.

  • Security
  • 15 minutes
  • Updated Oct 30, 2026

An agent with an API key in its environment can read it, print it into a log, commit it, or be talked into sending it somewhere by a web page, an issue, or a README it was asked to read. A key in a .env file or a secrets manager is no safer: the agent can read whatever its programs can.

On exe.dev the key never reaches the VM. You give it to an exe.dev integration once; the VM calls the integration's address, https://stripe.int.exe.xyz/, and exe.dev adds the key at the network edge on the way to the real API. The agent can use the API, but there is no key on the VM to read, copy, or leak. Set the VM's egress policy to integrations, and the agent can't reach anything else either.

This page sets that up for one agent VM: models through the LLM integration, the Stripe API through an HTTP proxy integration as the example of "any API with a key", and a VM that can reach only those.

What you get#

  • Claude Code, Codex, and pi answering through the LLM integration, with no model key on the VM
  • The Stripe API at https://stripe.int.exe.xyz, with a restricted key exe.dev adds outside the VM
  • Agents that know where their APIs are and never ask for a key
  • A VM that reaches its integrations and nothing else
What's running, and where
Private ssh my-agent-box.exe.xyz the agent's terminal
Private https://my-agent-box.shelley.exe.xyz/ Shelley, the agent in your browser
my-agent-box.exe.xyz exe.dev VM
Your agent Claude Code, Codex, pi, or Shelley, preinstalled ~/.claude, ~/.codex, ~/.pi
LLM integration models at llm.int.exe.xyz, no model key on the VM
HTTP proxy integration Stripe at stripe.int.exe.xyz, key added by exe.dev
Egress policy integrations only; nothing else leaves the VM

How it works#

agent on my-agent-box                exe.dev edge                        api.stripe.com
GET https://stripe.int.exe.xyz/  ──▶  is stripe attached to this VM?  ──▶  GET /v1/customers
    v1/customers                      add Authorization: Bearer rk_…       (sees the key)
    (no key)                          (the VM never sees it)
  • The key lives with exe.dev. You type or pipe it into ssh exe.dev integrations add, or paste it on the Integrations page. integrations list --json shows it as ***.
  • Only attached VMs get through. From any other VM, the address answers 403 integration not found or not attached to this VM.
  • The agent can't swap in its own credential. If a request already has the header the integration adds, exe.dev replaces it.
  • You can take it back without touching the VM. Detach the integration and access stops; rotate the key with integrations edit and the agent never notices.

Create the agent VM#

On your computer, create a VM (pick any free name) and connect:

ssh exe.dev new --name=my-agent-box
ssh my-agent-box.exe.xyz

Claude Code, Codex, pi, and Shelley are already installed. On the VM, see what it can reach without a key:

curl -s https://reflection.int.exe.xyz/integrations | grep '"name"'

New accounts have two integrations attached to every VM: llm, the models, and reflection, which tells the VM about itself and its integrations.

Models, with no model key#

The llm integration serves Anthropic's and OpenAI's APIs at https://llm.int.exe.xyz, using your exe.dev LLM credits. On the VM, point Claude Code and Codex at it:

exeuntu configure claude
exeuntu configure codex

That writes ~/.claude/settings.json and ~/.codex/config.toml with the integration's address and a placeholder in place of an API key. pi and Shelley find the integration by themselves. Check all three:

claude -p "Reply with exactly: claude ok" < /dev/null
codex exec --skip-git-repo-check "Reply with exactly: codex ok" < /dev/null
pi -p "Reply with exactly: pi ok" < /dev/null

Each prints its ok.

Use your own provider key#

To bill a model provider directly, put your key in an LLM integration of its own. On your computer, at the ssh exe.dev prompt, which asks for the key with hidden input:

exe.dev ▶ integrations add llm --name my-keys --anthropic=byok --anthropic-key=- --openai=disabled --fireworks=disabled --attach vm:my-agent-box

Then use https://my-keys.int.exe.xyz wherever the steps above use https://llm.int.exe.xyz (in ~/.claude/settings.json, for Claude Code). --openai=byok --openai-key=- does the same for OpenAI; the LLM integration docs cover ChatGPT subscriptions, custom endpoints, and keyless workload identity.

Any API with a key: Stripe#

An HTTP proxy integration forwards https://<name>.int.exe.xyz/<path> to the API you name and adds a header on the way: a bearer token here, or any --header Name:value. Stripe is the example, because a Stripe key is one you'd least like to see in an agent's transcript.

  1. In the Stripe Dashboard, in test mode, create a restricted key with Read access to Customers and nothing else. It starts rk_test_.

  2. On your computer, give it to exe.dev. At the ssh exe.dev prompt, which asks for the key with hidden input:

    exe.dev ▶ integrations add http-proxy --name stripe --target https://api.stripe.com --bearer=- --comment="Stripe API, test mode, read-only customers. Call https://stripe.int.exe.xyz/v1/... with no key." --attach vm:my-agent-box
    

    Or pipe it from a password manager, so it never sits in your shell: op read "op://vault/stripe/restricted-key" | ssh exe.dev integrations add http-proxy --name stripe --target https://api.stripe.com --bearer=- --attach vm:my-agent-box. Then delete the key from wherever you copied it.

  3. On the VM, call Stripe with no key:

    curl -s "https://stripe.int.exe.xyz/v1/customers?limit=1"
    

    Expect "object": "list" and "url": "/v1/customers". Asking for anything else, such as /v1/balance, gets a 403 saying the key does not have the required permissions: it can do only what you allowed.

Tip

The same pattern works for most of the services agents are given keys for. The integration catalog has ready-made integrations for over a hundred (Stripe, Linear, Sentry, Cloudflare, search APIs, and more) that know each service's header and address, and the GitHub integration covers git and gh for chosen repositories.

Tell the agents where their APIs are#

Agents don't know about integrations until told. On the VM, add a note to the instructions Claude Code, Codex, and pi read at the start of every session:

mkdir -p ~/.pi/agent
for f in ~/.claude/CLAUDE.md ~/.codex/AGENTS.md ~/.pi/agent/AGENTS.md; do
  cat >> "$f" <<'EOF'

## API access

This VM reaches outside APIs through exe.dev integrations, which add the
credentials outside the VM. `curl -s https://reflection.int.exe.xyz/integrations`
lists them, with notes on how to use each. Call
`https://<name>.int.exe.xyz/...` with no API key. Never ask for API keys, and
never write one into a file, an environment variable, or a commit.
EOF
done

Shelley reads the integration list by itself. Now ask for something that needs Stripe, without saying how:

mkdir -p ~/work && cd ~/work
claude -p "Who are my three newest Stripe customers?" --allowedTools "Bash(curl:*)" < /dev/null

Claude reads the integration list, finds stripe and its comment, and answers from https://stripe.int.exe.xyz/v1/customers. In an interactive session, approve the curl when it asks.

Note

Codex runs commands in a sandbox with no network by default. To let it call integrations, start it with codex --sandbox workspace-write -c sandbox_workspace_write.network_access=true, or approve each command when it asks.

Let it reach nothing else#

With no keys on the VM, the remaining risk is data: an agent that has read your customers could still be talked into posting them to someone's server. On your computer, allow only integrations:

ssh exe.dev egress my-agent-box integrations

On the VM, check both sides:

curl -sS -m 5 https://example.com; echo "exit $?"
curl -s https://llm.int.exe.xyz/v1/models | head -c 80; echo

The first fails with Could not resolve host: example.com; the second still lists models. The agent can still talk to you over SSH and Shelley, and serve pages at https://my-agent-box.exe.xyz/.

Package installs, git clone from the internet, and web browsing stop too. Open the VM while you set it up, and close it again before handing it work:

ssh exe.dev egress my-agent-box all
ssh exe.dev egress my-agent-box integrations

To start every agent VM locked, create it with ssh exe.dev new --name=my-agent-box --egress=integrations; Claude Code, Codex, and pi are already on it.

Grant, limit, and revoke#

All of these run on your computer, and none of them touch the VM:

# Access for one job: it lapses by itself after two hours.
ssh exe.dev integrations attach stripe vm:my-agent-box --for 2h

# Cut access now.
ssh exe.dev integrations detach stripe vm:my-agent-box

# Rotate the key; the agent keeps the same address.
op read "op://vault/stripe/restricted-key" | ssh exe.dev integrations edit stripe --bearer=-

# Your integrations and where they're attached; keys show as ***.
ssh exe.dev integrations list --json

Without a password manager, run integrations edit stripe --bearer=- at the ssh exe.dev prompt, which asks for the key with hidden input. To give many agent VMs the same access, tag them (ssh exe.dev tag my-agent-box agents) and attach to the tag: integrations attach stripe tag:agents.

Let the agent ask#

Shelley, or any agent, can ask you for access with a link that fills in the form on exe.dev, such as https://exe.dev/integrations/add?service=stripe&attach=vm:my-agent-box&for=8h (handles are in the catalog). You open it, review it, and paste the key on exe.dev; the key never passes through the agent, and the link carries none.

Warning

The edge adds the key to requests, not responses. An API that sends a credential back (an echo service, some token endpoints) hands it to the agent, and it lands in the agent's transcript. Don't point integrations at those with a real key; for token endpoints, use the catalog's token-mint integrations, which strip it.

Troubleshooting#

  • 403 integration not found or not attached to this VM: the name is wrong or the integration isn't attached here. On the VM, curl -s https://reflection.int.exe.xyz/integrations lists what is; on your computer, ssh exe.dev integrations attach <name> vm:my-agent-box.
  • 401 and Invalid API Key provided: the stored key is wrong or was rolled in Stripe. integrations edit stripe --bearer=- replaces it.
  • 403 and does not have the required permissions: the restricted key doesn't allow that call. Edit the key's permissions in Stripe; nothing changes on exe.dev.
  • Could not resolve host for anything but *.int.exe.xyz: the egress policy is integrations. ssh exe.dev egress my-agent-box shows it.
  • Claude Code asks you to log in: run exeuntu configure claude again, or check ~/.claude/settings.json for ANTHROPIC_BASE_URL. If it says existing user config, it left your settings alone; the LLM integration docs show the two settings it needs.
  • The agent asks you for a key: the note in CLAUDE.md or AGENTS.md is missing, or the integration has no --comment. Point it at https://reflection.int.exe.xyz/integrations.

Frequently asked questions#

Can the agent read the key?

No. The key is stored by exe.dev and added to the request after it leaves the VM, so it isn't in any file, environment variable, process, or packet on the VM. integrations list --json shows it as ***, even to you. A prompt injection, a malicious package, or someone with a shell on the VM can use the API while the integration is attached, but has no key to copy.

What stops the agent from misusing the API?

The key's own permissions, and how long you attach it. Give the integration the narrowest key the service offers (a Stripe restricted key, a read-only token, a GitHub integration with --readonly), attach it --for the length of the job, and detach it to cut access at once. The egress policy keeps the agent from sending what it reads anywhere else.

How is this different from a secrets manager or a .env file?

A secrets manager hands the key to the process that needs it, so the agent running that process can read it. Here the agent only ever holds an address, https://stripe.int.exe.xyz, which works from VMs the integration is attached to and nowhere else.

Which agents work with this?

Any agent or program that lets you set an API's base URL. Claude Code, Codex, pi, and Shelley come preinstalled and set up for the LLM integration; SDKs take a base URL and accept a placeholder key. Tools that call an API with curl just use the integration's address.

What does it cost?

With the default llm integration, model calls use your exe.dev LLM credits. With your own provider key, the provider bills you as usual. Calls through an HTTP proxy integration are billed by that API, to the account the key belongs to.

Can several agent VMs share one integration?

Yes. Tag the VMs (ssh exe.dev tag my-agent-box agents) and attach the integration to the tag: integrations attach stripe tag:agents. Team integrations, made with --team, are shared by your team, attach by tag, and answer at https://<name>.team.exe.xyz.

Do I still need a sandbox for the agent?

The VM is the sandbox: its own kernel, disk, and network, separate from your laptop and your other VMs. Keeping keys out of it and limiting its egress covers the rest: what the agent can call, and where it can send what it finds.

Updated Oct 30, 2026 · Markdown · manifest.json · Something wrong? Tell us