---
title: "Give agents API access without giving them the keys"
description: "Let coding agents call models, Stripe, or any API from an exe.dev VM while the keys stay outside it, added at the network edge where no agent can read them."
url: https://exe-staging.dev/deploy/agent-secrets
category: "Security"
updated: 2026-10-30
---

# Give agents API access without giving them the keys

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

- **Software:** Agent secrets (https://exe.dev/docs/integrations)
- **Time:** 15 minutes
- **Machine-readable:** https://exe-staging.dev/deploy/agent-secrets/manifest.json

## 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

Running on the VM:

- **Your agent**: Claude Code, Codex, pi, or Shelley, preinstalled, data in `~/.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 is reached:

- ssh my-agent-box.exe.xyz (private): the agent's terminal
- https://my-agent-box.shelley.exe.xyz/ (private): Shelley, the agent in your browser

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](/docs/integrations) 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](/docs/internet-access) 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](/docs/integrations-llm), the Stripe API through an
[HTTP proxy integration](/docs/integrations-http-proxy) as the example of
"any API with a key", and a VM that can reach only those.

## How it works

```text
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). `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:

```bash
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:

```bash
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:

```bash
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:

```bash
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](/docs/integrations-llm) cover ChatGPT subscriptions,
custom endpoints, and keyless workload identity.

## Any API with a key: Stripe

An [HTTP proxy integration](/docs/integrations-http-proxy) 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](https://dashboard.stripe.com/test/apikeys), 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:

   ```bash
   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](/docs/integrations-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](/docs/integrations-github) 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:

```bash
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:

```bash
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:

```bash
ssh exe.dev egress my-agent-box integrations
```

On the VM, check both sides:

```bash
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:

```bash
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:

```bash
# 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](/docs/integrations-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](/docs/integrations-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](/docs/integrations-llm#use-with-claude-code) 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](/docs/shelley/llm-gateway). 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](/docs/teams/overview), 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.


## Related

- [Run Claude Code on a remote server](https://exe-staging.dev/deploy/claude-code.md): A computer for Claude. A breather for your laptop.
- [Run Codex on a remote server](https://exe-staging.dev/deploy/codex.md): A computer for Codex. An early night for your laptop.
- [Host OpenClaw](https://exe-staging.dev/deploy/openclaw.md): An AI sidekick with its own computer. Your laptop can take a nap.
- [Self-host Hermes Agent](https://exe-staging.dev/deploy/hermes-agent.md): An agent that remembers you, on a computer that never sleeps.
- [Give any agent a sandbox over MCP](https://exe-staging.dev/deploy/exe-mcp.md): One URL gives your agent real VMs to run code on, host apps from, and hand work to.
- [Self-host n8n](https://exe-staging.dev/deploy/n8n.md): Connect your apps. Let the boring bits run themselves.
