> ## Documentation Index
> Fetch the complete documentation index at: https://docs.opper.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Restrict API keys by IP

> Limit a runtime API key to the IP addresses and CIDR ranges your servers call from. Calls from any other address are refused with 403 ip_not_allowed.

A runtime API key can carry a list of **allowed IPs**: the public IPv4 and IPv6
addresses and CIDR ranges it accepts calls from. A call made with that key from
any other address is refused before it reaches a model. If the key leaks, it is
useless outside your network.

The list is set per key. There is no organization or project setting, so each
key can have its own list, and keys without one keep working from anywhere.

* **Empty list (the default):** the key accepts calls from any IP. Existing keys
  are unaffected until you add a list.
* **One or more entries:** the key accepts calls only from those addresses.

Allowed IPs apply to runtime (inference) keys, the `op-…` keys you call models
with. [Management keys](https://docs.opper.ai/control-plane/management-api.md) are not covered, and
keys created by the CLI or agent sign-in flow cannot have a list.

## What the list accepts

Each entry is a single address or a CIDR range:

```text theme={null}
203.0.113.7
198.51.100.0/24
2001:db8::/32
```

* Up to **100 entries** per key.
* **Public addresses only.** Private, loopback, link-local, multicast, CGNAT
  (`100.64.0.0/10`) and unspecified addresses are refused, because Opper never
  sees a call coming from them.
* A range can be no wider than **/8** for IPv4 or **/16** for IPv6.
* Write ranges in CIDR form. Dash ranges such as `203.0.113.4-203.0.113.20` are
  not accepted.
* Entries are stored normalized. A range written with host bits set, such as
  `198.51.100.9/24`, is saved as `198.51.100.0/24`.

An entry that breaks a rule is rejected when you save, with a message naming
the entry and the reason. Nothing is saved until the whole list is valid.

## Set allowed IPs in the platform

Runtime keys are under **Settings**, then **API keys**, then **Inference keys**.

* **On a new key:** in the **Create API key** dialog, fill in the
  **Allowed IPs** box with one address or range per line. Leave it empty to
  accept calls from any IP.
* **On an existing key:** open the key's details panel and choose **Edit** on
  the **Allowed IPs** row. Clear the box to remove the list.

## Set allowed IPs from code

The [Management API](https://docs.opper.ai/control-plane/management-api.md) takes the same list as
`allowed_ips`, an array of strings. The key needs the `apikeys:write` scope to
set it and `apikeys:read` to list it.

**1. Create a key with a list**

```bash theme={null}
curl -X POST https://api.opper.ai/management/v1/projects/$PROJECT_UUID/api-keys \
  -H "Authorization: Bearer $OPPER_MANAGEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name": "checkout-prod", "allowed_ips": ["203.0.113.7", "2001:db8::/32"]}'
```

**2. Change or clear the list on an existing key**

```bash theme={null}
curl -X PATCH https://api.opper.ai/management/v1/projects/$PROJECT_UUID/api-keys/$KEY_ID \
  -H "Authorization: Bearer $OPPER_MANAGEMENT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"allowed_ips": ["203.0.113.7", "198.51.100.0/24"]}'
```

The list you send replaces the old one. Send `"allowed_ips": null` or `[]`
to clear it, so the key accepts calls from any IP again.

**3. Check what each key allows**

`GET /management/v1/projects/{uuid}/api-keys` returns `allowed_ips` for
every key, as `[]` for a key without a list.

Over [MCP](https://docs.opper.ai/developer-tools/mcp.md), `create_api_key` takes `allowed_ips`, and
`update_api_key` sets or clears the list on an existing key.

## When a call is refused

A call from an address that is not on the key's list gets HTTP `403` with the
header `X-Opper-Error-Code: ip_not_allowed` and this body:

```json theme={null}
{
  "code": "ip_not_allowed",
  "error": "ip_not_allowed",
  "client_ip": "192.0.2.10",
  "message": "This API key only accepts requests from its allowed IP addresses. This request came from 192.0.2.10."
}
```

`client_ip` is the address Opper saw. If the call was legitimate, add that
address (or the range it belongs to) to the key's list.

Rarely, a call with a listed key gets `503` with the error
`client_address_unavailable` and a `Retry-After` header. Opper could not verify
the caller's address at that moment. Retry after the delay.

## Things to know

* **Changes take up to a minute.** A new or changed list can take up to a
  minute to apply on every Opper server. Revoking a key still takes effect
  immediately, so revoke a key you believe is compromised rather than relying
  on a list change.
* **Opper sees your egress address.** Behind NAT, a proxy, or a corporate
  network, that is the public address traffic leaves from, not the machine's
  own address. Add the egress addresses of every place the key is used from
  (each server, CI runner or office network).
* **List your IPv6 addresses too.** The API is reachable over IPv6, so a
  machine with IPv6 connectivity often calls over IPv6 even when you expected
  IPv4. If your network has IPv6, add its IPv6 address or range as well. To see
  which address you call from, run `curl -4 https://checkip.amazonaws.com` and
  `curl -6 https://checkip.amazonaws.com`, or read `client_ip` from a refused
  call.
* **Forwarded headers are ignored.** An `X-Forwarded-For` or similar header set
  by the caller does not change the address Opper checks.
* **The Playground and platform features skip listed keys.** Calls that Opper's
  own platform makes for you, such as the Playground, use keys without a list.
  A key with allowed IPs is not offered there.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.