# How to give Claude Code access to your API without giving it your keys

> Connect Claude Code to your API through Porticol, keep the key in the keychain, and hold a risky call for approval.

Canonical: https://porticol.app/docs/guides/claude-code-api-access

[View as Markdown](https://porticol.app/docs/guides/claude-code-api-access.md)

Claude Code works best when it can call the API it is changing. The usual shortcut puts a production key in the agent's settings or an `.env` file, where the agent and anything it runs can read it.

Porticol Guard gives the agent the API without the key. Calls go through Porticol's MCP server, the key is injected inside Porticol, and a risky call waits for you.

## 1. Install

macOS or Linux:

```sh
curl -fsSL https://porticol.app/install.sh | sh
```

Windows, in PowerShell:

```powershell
irm https://porticol.app/install.ps1 | iex
```

Open Porticol. Local use needs no account and no sign-in. An account is asked for only when you invite someone, sync, share or use a team gateway.

## 2. Connect Claude Code

On the start screen, choose **Connect my coding agent**. Porticol finds Claude Code, shows the exact lines it will add to `~/.claude.json`, and writes nothing until you click **Add Porticol**. **Remove** restores the file.

By hand, add the server at user scope:

```sh
claude mcp add --scope user porticol -- porticol-cli mcp
```

Restart Claude Code or reload MCP, then ask it to list your Porticol collections. The first call proves the connection.

For level 2 on macOS or Linux, let Porticol add the sandbox settings: the sandbox on, `sandbox.network.httpProxyPort` pointing at the local proxy, read denied for `.env*`, `~/.aws`, `~/.config/gcloud` and similar, and a `PreToolUse` hook that fails closed. The screen shows every line before it writes one.

## 3. Write a policy

Guard reads one YAML policy. Start in shadow mode, where Guard logs what it would do and lets the call through, then switch to `enforce` when the rules match how your team works. The policy below is in `enforce`, because the walkthrough holds a call.

```yaml
version: 1
mode: enforce
hosts:
  api.example.com: { env: production, key: "{{api_key}}" }
rules:
  - match: { method: [GET, HEAD] }
    action: allow
  - match: { env: production, method: [POST, PUT, PATCH] }
    action: ask
agents:
  claude-code: { up_to: production }
  "*": { up_to: staging }
```

`{{api_key}}` names a secret variable of the production environment. Its value stays in the keychain, and Porticol adds it to the request.

The first matching rule wins. A call above the agent's ceiling is denied before any rule. On a protected environment, a call no rule matches asks by default, so a DELETE to production is held.

## 4. The held call

When Claude Code sends a DELETE to production, `send` returns:

```json
{
  "status": "pending",
  "approval_id": "8f0c…",
  "reason": "DELETE to production needs a person.",
  "message": "A person must approve this call in Porticol. Call send again with the same request and approval_id once they have. Do not loop and do not ask for a secret."
}
```

The approval card shows the agent's name, the environment, the exact request with keys masked, and the agent's reason in its own words. Choose **Allow once**, **Allow for today** or **Deny**. A system notification fires while the app is in the background. From a terminal, `porticol-cli approve <id>` takes the four-letter code shown on the card; the agent's shell has no terminal, so it cannot approve its own call.

The approval is bound to a SHA-256 of the request the policy decided: the method, URL, environment, headers, auth, protocol, settings and body. It is single use and expires after 10 minutes. Claude Code retries once with the same request and `approval_id`; a changed request does not match. A denied call returns `{"status": "denied"}` and is not retried.

## 5. Read the log

Every call lands in Agent activity: the agent, method, URL, environment, decision and the rule behind it. The same entries sit in a hash-chained log. `porticol-cli guard log verify` checks the chain, and the log exports as JSON or CSV. Free keeps 30 days on this computer; on the server, Team keeps 90 days and Business 180, with export.

## The honest limit for Claude Code

Level 1 stops mistakes. An agent that finds a production key outside Porticol and runs `curl` itself is not stopped. Level 2 closes that on macOS and Linux by forcing sandboxed Bash through the proxy, but the sandbox covers Bash only: file tools, MCP servers, WebFetch and hooks run outside it, and a person can turn the sandbox off unless managed settings forbid it. Windows runs levels 1 and 3 only. Claude Code's name in the log is self-reported.

## Questions

**Does Claude Code ever see the key?** No. The key is injected inside Porticol when the request is sent, and every result is masked. A key that already sits in an `.env` file is a separate problem: `porticol-cli guard scan` finds those, and Porticol offers to move each value to the keychain.

**What happens if nobody answers?** The MCP call waits up to 45 seconds, then returns `pending` with the approval id. The approval stays open for 10 minutes, and the agent may retry once after you approve it.

**Can I approve from a terminal?** Yes. Run `porticol-cli approve <id>` and type the four-letter code from the card. An agent's shell has no terminal, which is the point.
