Status: Optional but strongly recommended. When configured, only listed source IPs may call the API with that key.
When a Seller API key has a non-empty allowed_ips array, only requests from
those public IPv4/IPv6 addresses (or CIDR ranges) are accepted. Requests from
any other IP are rejected with a structured 403.
When allowed_ips is NULL or empty, the key accepts requests from any
source IP. The dashboard still recommends configuring an allowlist to reduce
key-leakage risk.
Why this matters#
A leaked API key is the single highest-impact failure mode for a digital distribution platform: an attacker who steals the key can pull inventory keys, issue refunds, and disable webhooks until the key is revoked.
Pinning each key to a small, customer-declared egress fingerprint changes the attack model from "steal the key, exfiltrate from anywhere" to "steal the key AND get on-network with one of N declared IPs".
What you can declare#
An optional array of 1–50 entries on the key's allowed_ips field.
When present, each entry is either:
| Shape | Example | Notes |
|---|---|---|
| IPv4 address | 203.0.113.5 | Strict dotted-quad |
| IPv4 CIDR | 203.0.113.0/24 | Prefix 1–32. /0 is rejected. |
| IPv6 address | 2001:db8::1 | Lower-cased; ::ffff: v4-mapping ok |
| IPv6 CIDR | 2001:db8::/48 | Prefix 1–128. /0 is rejected. |
The following are rejected when configuring an allowlist:
- RFC1918 private (
10/8,172.16/12,192.168/16) - Loopback (
127/8,::1) - Link-local (
169.254/16,fe80::/10) - CGNAT (
100.64/10) - IPv6 ULA (
fc00::/7) - Multicast / documentation / benchmark (
224/4,192.0.2/24,198.51.100/24,203.0.113/24) - Anything that doesn't normalise to a valid literal
Error contract#
All denials return a 403 with a structured JSON body following the standard
flat ApiErrorResponse envelope (top-level code, not nested under error):
{
"code": "IP_NOT_ALLOWED",
"message": "Client IP 198.51.100.42 is not in this key's allowed_ips list. Update the key's allowed IPs in the Avrix dashboard (Settings → Integrations → API Keys) and retry.",
"details": {
"clientIp": "198.51.100.42",
"allowedIpsCount": 3,
"docsUrl": "https://docs.avrix.io/seller-api/ip-allowlist"
},
"requestId": "req_...",
"doc_url": "https://docs.avrix.io/seller-api/ip-allowlist"
}
code | When |
|---|---|
| IP_NOT_RESOLVABLE | Key has a non-empty allowlist but the request IP could not be determined from trusted proxy headers |
| IP_NOT_ALLOWED | Key has a non-empty allowlist and the client IP did not match any entry |
We never echo the allowlist itself in the error body. We only confirm or deny — and on deny we echo the client's IP back so you can update the key.
Cache semantics#
Successful key authorisations may be cached briefly (~60s). The IP check is re-evaluated on every request, including cache hits. This means:
- Removing an IP via the dashboard takes effect within ~60s on cold caches.
- A request from a new IP is denied immediately even if a previous request from a different IP just primed the cache.
Endpoints#
Key-management endpoints authenticate with an ordinary Seller API key that
holds the seller:keys:write scope — there is no separate admin
credential. A key minted over the API can never exceed the creating key's
ceiling: scopes are intersected with the parent's, expiry is inherited, and
allowed_ips must be a non-empty subset of the parent's list whenever the
parent is itself IP-restricted.
POST /api/seller/v1/keys#
Create a new key. allowed_ips is optional (omit or pass [] for any
source IP; 1–50 entries when configured). Pass includeFinanceScope: true
only when the creating key already holds seller:finance:read.
curl -X POST https://api.avrix.io/api/seller/v1/keys \
-H "Authorization: Bearer ${AVRIX_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "prod-fulfillment",
"allowed_ips": ["203.0.113.5", "203.0.113.0/24"]
}'
The 201 response is the only time the secret is returned:
{
"data": {
"id": "9f3c…",
"name": "prod-fulfillment",
"key": "avrix_sk_…",
"livemode": true
}
}
PATCH /api/seller/v1/keys/{id}/allowed-ips#
Replace the IP allowlist on an existing key without rotating the secret. Useful when your servers move egress IPs.
curl -X PATCH https://api.avrix.io/api/seller/v1/keys/${KEY_ID}/allowed-ips \
-H "Authorization: Bearer ${AVRIX_API_KEY}" \
-H "Content-Type: application/json" \
-d '{"allowed_ips": ["203.0.113.5", "2001:db8::/48"]}'
Returns 204 No Content on success.
GET /api/seller/v1/whoami-ip#
Public, unauthenticated. Echoes the request's normalised client IP. Used by:
- The dashboard's "Detect my IP" button (browser-side onboarding).
- Customer-side
curlfrom their integration host to confirm their egress IP before they put it in the allowlist.
curl https://api.avrix.io/api/seller/v1/whoami-ip
# → { "data": { "clientIp": "203.0.113.5" }, "meta": { "requestId": "req_..." } }
The endpoint never echoes any other request signal and never auths — the IP is already implicit in every request and is not itself a secret.
Header precedence (how we determine your IP)#
Client IP is taken from trusted proxy headers, in order:
- The edge platform's own forwarded-for header, when present.
x-real-ip— set by upstream proxies in custom deployments.x-forwarded-for— first (leftmost) hop.
If none of these resolve to a normalised literal we return IP_NOT_RESOLVABLE.
Configure your reverse proxy to forward x-forwarded-for if you terminate TLS
yourself.
Rollout policy#
- New keys:
allowed_ipsis optional at creation (POST /api/seller/v1/keys). Omit the field or pass an empty array to accept any source IP. - Existing keys: add or replace entries via Settings → Integrations → API Keys or PATCH /api/seller/v1/keys/{id}/allowed-ips without rotating the secret.
- Enforcement: runtime only when
allowed_ipsis non-empty.
See also#
- Scope matrix — scopes required before keys can call the API
- Sandbox — test IP allowlist and auth triage without production traffic
- Error playbook —
IP_NOT_ALLOWEDand recovery steps