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

# Per-API-key IP allowlist

> Restrict an API key to a specific set of source IP addresses or CIDR ranges.

# Per-API-key IP allowlist

Bind an Orbit API key to one or more source IPs (or CIDR ranges) so the key only authenticates when the request originates from your infrastructure. A leaked key is then useless to anyone outside your network — the request fails with `401 INVALID_API_KEY` before it touches your billing or tenant data.

This is a per-key control. The org-wide [IP allowlist](/api-reference/settings) (under **Settings → Security**) gates the dashboard and SAML login surfaces; the per-key list gates programmatic API authentication.

## Constraints

* Up to **50** entries per key.
* Each entry must be a valid IP (v4 or v6) or CIDR range. The auth package validates each entry before storage.
* An empty list (`[]`) or `null` clears the allowlist — the key becomes unrestricted again.
* Changes take effect on the next request — there is no propagation lag.

## Read the current allowlist

Owner / admin / developer scope.

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/settings/api-keys/{keyId}/allowed-ips \
  -H "X-API-Key: dv_live_sk_..."
```

Response:

```json theme={null}
{
  "data": {
    "id": "key_abc123",
    "allowed_ips": ["203.0.113.42", "198.51.100.0/24"]
  },
  "meta": {
    "request_id": "req_xyz789",
    "timestamp": "2026-05-09T10:14:22Z"
  }
}
```

`allowed_ips` is `null` when the key is unrestricted.

## Replace the allowlist

`PATCH` is replace-only — it never merges. To add an entry, fetch the list, append, and PATCH the full list back.

```bash theme={null}
curl -X PATCH https://api.orbit.devotel.io/api/v1/settings/api-keys/{keyId}/allowed-ips \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "allowed_ips": [
      "203.0.113.42",
      "198.51.100.0/24",
      "2001:db8::/32"
    ]
  }'
```

## Clear the allowlist (make unrestricted)

```bash theme={null}
curl -X PATCH https://api.orbit.devotel.io/api/v1/settings/api-keys/{keyId}/allowed-ips \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "allowed_ips": null }'
```

## What a blocked request looks like

When a key has `allowed_ips` set and a request arrives from a non-allowed source IP:

```http theme={null}
HTTP/1.1 401 Unauthorized
Content-Type: application/json

{
  "error": {
    "code": "INVALID_API_KEY",
    "message": "The provided API key is invalid or has been revoked."
  }
}
```

The error returns generic `INVALID_API_KEY` rather than `IP_NOT_ALLOWED` so a leaked key can't enumerate the allowlist by trial-and-error. For the same reason the rejection is deliberately silent — blocked requests are **not** recorded under a dedicated audit-log action, so don't build forensics on an `api_key.allowed_ips_violation` event (none is emitted). Use your edge/gateway access logs to investigate denied source IPs.

## Audit log

Every replacement of the allowlist writes an audit-log entry under `api_key.allowed_ips_updated` with:

* `actor_user_id`
* `resource_id` — the API key id
* `details.count` — the number of entries in the new list
* `ip_address` — the operator's source IP

Combine with the dashboard at **Settings → Audit Log** to track who changed which key when.

## Validation rules

| Input                          | Behaviour                                                              |
| ------------------------------ | ---------------------------------------------------------------------- |
| `null`                         | Clears the list. Key becomes unrestricted.                             |
| `[]` (empty array)             | Clears the list. Key becomes unrestricted.                             |
| Valid IP (`203.0.113.42`)      | Stored as a `/32` (v4) / `/128` (v6) match.                            |
| Valid CIDR (`198.51.100.0/24`) | Stored as-is; matched as a range.                                      |
| IPv6 / IPv6 CIDR               | Supported — `2001:db8::/32` is valid.                                  |
| Malformed entry                | Whole request rejected with `422 VALIDATION_ERROR`; nothing persisted. |
| > 50 entries                   | Whole request rejected with `422 VALIDATION_ERROR`.                    |

## See also

* [Settings API → IP allowlist](/api-reference/settings) — org-wide allowlist for the dashboard / SAML.
* [Authentication](/authentication) — API key formats and rotation.
* [Webhooks → Security](/webhooks/security) — signing for inbound webhooks.
