Automating NetBox IP Address Inventory with Paginated REST API Calls
Learn how to pull a complete NetBox IP address inventory using the paginated REST API, handle rate limits, and export a CSV ready for audits or automation.
27 Nov 2025, 07:27 UTC

The problem: manual IP audits don’t scale
NetBox’s UI is great for ad‑hoc lookups, but when you need a full inventory across dozens of sites the click‑through approach becomes a time sink and a source of errors. The REST API lets you pull every IP address programmatically, and the built‑in cursor pagination makes it practical even for large deployments.
API basics you need to know
- Endpoint:
/api/ipam/ip-addresses/returns a JSON object withcount,next,previousand aresultsarray. - Authentication: a token header
Authorization: Token <your‑api‑token>. Create a read‑only token in NetBox → Admin → API Tokens and store it in an environment variable (e.g.,NETBOX_TOKEN). - Page size: default is 50 records; you can raise it with
?limit=200but thenextlink will always reflect the server‑side limit.
Walking the pagination chain
The first request returns a next URL when more pages exist. A simple loop follows that URL until it becomes null. Each iteration appends the page’s results to a local list or streams them directly to disk.
import os, requests, csv, time
BASE = "https://netbox.example.com/api/ipam/ip-addresses/"
HEADERS = {"Authorization": f"Token {os.getenv('NETBOX_TOKEN')}"}
PARAMS = {"limit": 200} # optional, reduces round‑trips
def fetch_all():
url = BASE
while url:
resp = requests.get(url, headers=HEADERS, params=PARAMS if url == BASE else None)
if resp.status_code == 429: # rate limit
retry = int(resp.headers.get("Retry-After", "5"))
time.sleep(retry)
continue
resp.raise_for_status()
data = resp.json()
yield from data["results"]
url = data["next"]
Run the generator from a script or REPL; it yields one IP‑address record at a time, keeping memory usage low.
Handling rate limits gracefully
NetBox enforces a configurable request‑per‑minute quota. The example above respects 429 Too Many Requests by reading the Retry-After header (or falling back to 5 seconds) and retrying. For production you may want exponential back‑off and a maximum retry count.
Worked example: CSV inventory report
The following script writes address, device, vrf, role and description to ip_inventory.csv. It prints progress every 1 000 records so you can monitor long runs.
#!/usr/bin/env python3
import os, csv, sys
import requests
def main():
token = os.getenv("NETBOX_TOKEN")
if not token:
sys.exit("Set NETBOX_TOKEN environment variable")
headers = {"Authorization": f"Token {token}"}
url = "https://netbox.example.com/api/ipam/ip-addresses/"
params = {"limit": 200}
out_path = "ip_inventory.csv"
fields = ["address", "device", "vrf", "role", "description"]
with open(out_path, "w", newline="") as f:
writer = csv.DictWriter(f, fieldnames=fields)
writer.writeheader()
count = 0
while url:
resp = requests.get(url, headers=headers, params=params if url == "https://netbox.example.com/api/ipam/ip-addresses/" else None)
if resp.status_code == 429:
wait = int(resp.headers.get("Retry-After", "5"))
print(f"Rate limited, sleeping {wait}s")
time.sleep(wait)
continue
resp.raise_for_status()
data = resp.json()
for rec in data["results"]:
row = {
"address": rec["address"],
"device": rec["device"]["name"] if rec["device"] else "",
"vrf": rec["vrf"]["name"] if rec["vrf"] else "global",
"role": rec["role"]["name"] if rec["role"] else "",
"description": rec["description"] or ""
}
writer.writerow(row)
count += 1
if count % 1000 == 0:
print(f"Written {count} records…")
url = data["next"]
print(f"Done. {count} IP addresses exported to {out_path}")
if __name__ == "__main__":
main()
Where to run: any host with Python 3.8+ and the requests library (pip install requests). The script needs network access to the NetBox API endpoint and the NETBOX_TOKEN env var set to a read‑only token.
Trade‑offs and limitations
- Volume: Very large installations (hundreds of thousands of IPs) can still produce sizable CSV files. Stream to disk (as shown) rather than accumulating in memory.
- Token scope: A token with write permissions is a security risk if leaked. Always generate a read‑only token and rotate it periodically.
- API version drift: Field names (e.g.,
devicevsassigned_object) can change between NetBox releases. Pin the script to a tested NetBox version (v3.5+ at time of writing) and add a version check if you upgrade.
Verify the output
- Run the script against a local NetBox demo (Docker Compose from the official repo).
- Compare the CSV row count (excluding header) with the
countfield from the first API response; they should match. - Spot‑check a few entries in the NetBox UI under IPAM → IP Addresses to confirm field mapping.
Next steps
Schedule the script via cron or a CI job during off‑peak hours, compress the CSV (gzip ip_inventory.csv) and ship it to your CMDB or audit pipeline. If you need richer data (prefixes, VLANs, custom fields), extend the field list and adjust the extraction logic accordingly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.