Skip to content

IP pools and VIP operations

Only with own prefixes (a dedicated prefix from us or BYOIP), on shared or dedicated edges, see Shared and Dedicated. Your range is set up as IP pools bound to your company (sites). You assign their addresses (VIPs) to your vServices, release, move, swap, block or rotate them to another prefix, and announce standby pools. Requests need API access.

Endpoints

Method Path Key Purpose
GET /pools read List your IP pools
GET /pools/{scope} read One IP pool, address by address
GET /pools/{scope}/metrics read Traffic summed over one of your IP pools
POST /vip-operations write Run VIP operations as one change
GET /vip-operations/{op_id} read Where an operation stands

GET /pools

Returns: 200, list of PoolSummary

Answer 200
[
  {
    "scope": "P-A",
    "name": "a dedicated",
    "ipv4_range": "203.0.113.0/28",
    "ipv6_range": "2001:db8:a::/64",
    "customer_announce": false,
    "announced": true,
    "announce_changed_at": null,
    "next_change_at": null,
    "total": 16,
    "assigned": 2,
    "blocked": 0,
    "reserved": 0,
    "free": 14
  }
]

GET /pools/{scope}

Returns: 200, Pool

Answer 200
{
  "scope": "P-A",
  "name": "a dedicated",
  "ipv4_range": "203.0.113.0/28",
  "ipv6_range": "2001:db8:a::/64",
  "customer_announce": false,
  "announced": true,
  "announce_changed_at": null,
  "next_change_at": null,
  "total": 16,
  "assigned": 2,
  "blocked": 0,
  "reserved": 0,
  "free": 14,
  "addresses": [
    {
      "address": "203.0.113.0",
      "ipv6": "2001:db8:a::",
      "state": "free",
      "service": null,
      "blocked": false,
      "released_at": null
    },
    {
      "address": "203.0.113.1",
      "ipv6": "2001:db8:a::1",
      "state": "assigned",
      "service": "a.example.com",
      "blocked": false,
      "released_at": null
    },
    {
      "address": "203.0.113.2",
      "ipv6": "2001:db8:a::2",
      "state": "assigned",
      "service": "tcp.example.com",
      "blocked": false,
      "released_at": null
    }
  ]
}

GET /pools/{scope}/metrics

Parameter In Meaning
metric query Chart; one of traffic, response_codes, protection, pre_http_filter, l4_packets, l4_flows, traffic_by_service; default "traffic"
range query Time window; one of 5m, 15m, 1h, 6h, 24h, 7d; default "1h"

Returns: 200, MetricChart or TrafficByService

Answer 200: traffic
{
  "metric": "traffic",
  "range": "1h",
  "step": "15s",
  "series": [
    {
      "label": "at_site",
      "values": [
        [
          1792750389,
          1.5
        ],
        [
          1792750404,
          2.0
        ]
      ],
      "dashed": true,
      "direction": "in"
    },
    {
      "label": "inbound",
      "values": [
        [
          1792750389,
          1.5
        ],
        [
          1792750404,
          2.0
        ]
      ],
      "direction": "in"
    },
    {
      "label": "passed_frontend",
      "values": [
        [
          1792750389,
          1.5
        ],
        [
          1792750404,
          2.0
        ]
      ],
      "direction": "in"
    }
  ],
  "scope": {
    "pool": "P-A",
    "services": 2,
    "has_http": true,
    "has_l4": true,
    "partial_services": []
  }
}

POST /vip-operations

Parameter In Meaning
Idempotency-Key header Makes a retry return the first result; up to 128 characters

Body: VipOperations
Returns: 201, VipOperationRecord

Request: assign
{
  "operations": [
    {
      "op": "assign",
      "service": "a.example.com",
      "pool": "P-A",
      "count": 1
    }
  ]
}
Request: block
{
  "operations": [
    {
      "op": "block",
      "addresses": [
        "203.0.113.9"
      ]
    }
  ]
}
Answer 201: assign
{
  "id": "f69c6909151c",
  "idempotency_key": null,
  "at": "2026-10-23T10:13:25.336158+00:00",
  "by": "key:TGloIsNcCavQ",
  "services": [
    "a.example.com"
  ],
  "pools": [],
  "results": [
    {
      "op": "assign",
      "service": "a.example.com",
      "addresses": [
        "203.0.113.0"
      ]
    }
  ],
  "commit": "f3b4fa1574c766a5567fb442fee9989f5729792a",
  "replayed": false
}
Answer 201: block
{
  "id": "85b0a4fd6a65",
  "idempotency_key": null,
  "at": "2026-10-23T10:13:25.393986+00:00",
  "by": "key:TGloIsNcCavQ",
  "services": [],
  "pools": [],
  "results": [
    {
      "op": "block",
      "blocked": [
        "203.0.113.9"
      ]
    }
  ],
  "commit": "bbe37eb5bfcf90d2369090b89e5c6ebb0e7c3a13",
  "replayed": false
}

GET /vip-operations/{op_id}

Returns: 200, VipOperationStatus

Answer 200
{
  "id": "f69c6909151c",
  "idempotency_key": null,
  "at": "2026-10-23T10:13:25.336158+00:00",
  "by": "key:TGloIsNcCavQ",
  "services": [
    "a.example.com"
  ],
  "pools": [],
  "results": [
    {
      "op": "assign",
      "service": "a.example.com",
      "addresses": [
        "203.0.113.0"
      ]
    }
  ],
  "commit": "f3b4fa1574c766a5567fb442fee9989f5729792a",
  "state": "pending",
  "edges": []
}

Fields

Pools are read-only; every change is a POST /vip-operations. A vService shows its addresses in network.

PoolSummary

Field Type Notes
scope string Pool id, used in paths and operations
name string Display name
ipv4_range string IPv4 range of the pool
ipv6_range string IPv6 range of the pool
total integer IPv4 addresses in the range, first and last included
assigned integer Addresses on your vServices
blocked integer Addresses on your block list
reserved integer Addresses withheld by Zero Services
free integer Addresses neither assigned, reserved nor blocked
customer_announce boolean You switch the announcement
announced boolean The edges announce the prefixes now
announce_changed_at string may be null; When you last switched the announcement; null if never
next_change_at string may be null; Earliest time of the next announce or withdraw; null when possible now or not yours to switch

Pool

Field Type Notes
scope string Pool id, used in paths and operations
name string Display name
ipv4_range string IPv4 range of the pool
ipv6_range string IPv6 range of the pool
total integer IPv4 addresses in the range, first and last included
assigned integer Addresses on your vServices
blocked integer Addresses on your block list
reserved integer Addresses withheld by Zero Services
free integer Addresses neither assigned, reserved nor blocked
customer_announce boolean You switch the announcement
announced boolean The edges announce the prefixes now
announce_changed_at string may be null; When you last switched the announcement; null if never
next_change_at string may be null; Earliest time of the next announce or withdraw; null when possible now or not yours to switch
addresses list of object Every IPv4 address of the pool

VipOperations

Field Type Notes
operations (required) list of Operation at most 500; Up to 500 operations, applied in order; all succeed or none is applied
idempotency_key string up to 128 characters; Same as the Idempotency-Key header; when both are sent they must be equal

Operation

Field Type Notes
op Fields
assign service, and address or pool with count: the address unused the longest
release address
move address, to: another of your vServices
swap address; optional new_address or pool, default the next address of the same pool; block_old puts the old address on the block list
rotate_prefix from_pool, to_pool; keep_host keeps .17 as .17 where it is free
block, unblock addresses: never hand them out again, or again
announce, withdraw pool: only standby pools you switch yourself

VipOperationRecord

Field Type Notes
id string Operation id for GET /vip-operations/{op_id}
idempotency_key string may be null; The Idempotency-Key sent; null without one
at string When the operations were applied
by string Who ran them; key: for an API key
services list of string vServices the operations changed
pools list of string Pools whose announcement changed
results list of object One result per operation, in order: the op and what it did
commit string may be null; Configuration revision holding the change
replayed boolean true: a retry with the same Idempotency-Key, answered with the first result

VipOperationStatus

Field Type Notes
id string Operation id for GET /vip-operations/{op_id}
idempotency_key string may be null; The Idempotency-Key sent; null without one
at string When the operations were applied
by string Who ran them; key: for an API key
services list of string vServices the operations changed
pools list of string Pools whose announcement changed
results list of object One result per operation, in order: the op and what it did
commit string may be null; Configuration revision holding the change
replayed boolean true: a retry with the same Idempotency-Key, answered with the first result
state string one of pending, live, unknown; live: every reporting edge runs the change. pending: written, not yet live everywhere. unknown: the revision was not found
edges list of object Every edge that serves the changed vServices or announces the changed pools

Network

Field Type Notes
vips list of Vip at most 16; read-only; Change addresses with POST /vip-operations
pool string may be null; read-only; Change addresses with POST /vip-operations
edge_hostname string may be null; read-only; Change addresses with POST /vip-operations

Vip

Field Type Notes
anycast_ipv4 string may be null; read-only; Change addresses with POST /vip-operations
anycast_ipv6 string may be null; read-only; Change addresses with POST /vip-operations

Example

Swap an address, then check whether the change is live:

curl -s -X POST -H "Authorization: Bearer $ZP_KEY" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" $API/vip-operations \
  -d '{"operations": [{"op": "swap", "address": "198.51.100.17"}]}'
curl -s -H "Authorization: Bearer $ZP_KEY" $API/vip-operations/<id> | jq -r .state

Move everything from standby pool A to a fresh pool B:

# 1. announce B and wait until the operation is live
curl -s -X POST -H "Authorization: Bearer $ZP_KEY" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" $API/vip-operations \
  -d '{"operations": [{"op": "announce", "pool": "B"}]}'

# 2. move every address of A to B and withdraw A, in one step
curl -s -X POST -H "Authorization: Bearer $ZP_KEY" -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" $API/vip-operations \
  -d '{"operations": [{"op": "rotate_prefix", "from_pool": "A", "to_pool": "B"},
                      {"op": "withdraw", "pool": "A"}]}'

Traffic of a pool

GET /pools/{scope}/metrics sums the same metrics as GET /metrics over the vServices of one of your pools: those with every address in it.

curl -s -H "Authorization: Bearer $ZP_KEY" "$API/pools/A/metrics?metric=traffic&range=24h"
curl -s -H "Authorization: Bearer $ZP_KEY" "$API/pools/A/metrics?metric=traffic_by_service"
  • Only pools of your own range; any other pool is 404.
  • A vService with addresses in more than one pool is not summed in any of them, its counters are per vService. scope.partial_services names it.

Behaviour

  • All operations of one request are applied together or not at all.
  • Send an Idempotency-Key with every request: a retry after a timeout returns the first result (200, replayed: true) instead of changing twice.
  • state is live once every edge that serves the changed vServices or pools runs it, usually within a minute.
  • Keys of a delegation cannot use IP pools.
  • Standby pools are validated in advance (LOA, route object, ROA, upstream filters) and stay dark until you announce them: customer_announce means you switch the pool, announced that the edges announce it now.
  • Addresses come only from an announced pool; a pool is withdrawn only when none of its addresses is assigned.
  • The announcement of one pool changes at most once every 15 minutes; next_change_at says when it may change again. More often, upstream networks may suppress the prefix.
  • live means our edges announce the prefix; how fast the rest of the internet follows depends on the upstream networks.

In the portal: ZERO-PROTECT → IP Pools (main account and members with access; not delegations), with the traffic of each pool on its page.