# IP allowlist

An IP allowlist is an optional, per-key network control. When a key has at least one entry, the
API accepts the key only from a source IP that matches an entry. A key without an allowlist works
from any IP.

The allowlist supplements HMAC authentication. It never replaces the signature and it does not
grant scopes.

## Rules

| Rule | Value |
| --- | --- |
| Entry types | Exact IPv4 or IPv6 address, or CIDR range |
| Maximum entries | 50 per key |
| Address families | Each entry must match the family of the source IP |
| Hostnames | Not supported |
| Wildcards | Not supported |
| Empty allowlist | Any source IP |

Examples of accepted entries:

```text
203.0.113.10
203.0.113.0/24
2001:db8:1234::/48
2001:db8:1234::7
```

Examples that are rejected or never match:

```text
api.example.com        # hostnames are not resolved
203.0.113.*            # wildcards are not supported
::ffff:203.0.113.10    # map explicitly to the IPv4 family or use the IPv4 form
```

`0.0.0.0/0` and `::/0` are accepted by the parser but defeat the control. Do not use
them on live keys.

## Configure an allowlist

### Dashboard

The partner owner can set an allowlist when creating an agent key, or update the key from the
dashboard. Use commas or newlines between entries.

### API

`POST /v1/agents/{agentId}/keys` accepts an optional `ipAllowlist` array. The trailing 50
entries are kept if you send more.

```json
{
  "name": "production agent",
  "ipAllowlist": ["203.0.113.0/24", "2001:db8:1234::/48"]
}
```

The response contains the API ID and the secret once. It never returns the secret again.

## How the source IP is determined

The API derives the client IP in this order:

1. Read the peer address of the connection.
2. If the peer is a configured trusted proxy, prefer `CF-Connecting-IP`.
3. Otherwise use the first valid address in `X-Forwarded-For`.
4. If no trusted proxy header is valid, use the peer address.

Clients cannot choose their allowlisted address by setting `X-Forwarded-For` when they are not
coming through a trusted proxy. If your integration runs behind your own egress proxy, use the
proxy's public egress addresses in the allowlist.

## Failure behaviour

A mismatch returns:

```http
HTTP/1.1 403 Forbidden
Content-Type: application/json
X-Error-Code: ip_not_allowed

{"error":{"code":"ip_not_allowed","message":"This IP address is not allowed for this key."}}
```

`403 ip_not_allowed` is not a signature failure. Fix the network path or the allowlist. Do
not retry from the same address with a different nonce.

## Operational guidance

- Use one key per environment and per workload. An allowlist on a shared key couples unrelated
  systems to the same egress addresses.
- Use the narrowest range that covers the egress pool. Prefer `/32` for a single IPv4 host and
  `/128` for a single IPv6 host.
- Include IPv6 if your provider uses it. A dual-stack host may reach the API over IPv6 even when
  you tested over IPv4.
- Update the allowlist before changing an egress pool. A key that suddenly fails is often a
  network change, not an authentication change.
- Monitor `403 ip_not_allowed` responses. A sustained rise can mean a misconfigured proxy or a
  credential used from an unexpected network.
- Treat the allowlist as defence in depth. It is not a substitute for protecting the secret.

## Troubleshooting

| Check | Action |
| --- | --- |
| What address does the API see? | Log the public egress address from the calling host, not the internal container address. |
| Is the key allowlist empty? | An empty list means any IP. Check the key you actually used. |
| Is the address IPv6? | Add the IPv6 range as well as the IPv4 range. |
| Is the CIDR aligned? | `203.0.113.10/24` is not a valid network; use `203.0.113.0/24`. |
| Is the key expired? | Rotate the key, then re-check the allowlist on the new key. |

For scope failures after a valid signature, see [Authentication](/docs/authentication). For request
errors, see [Errors](/docs/errors).
