Infrastructure inventory automation and utility/integration operations for netbox-proxmox-sync, an idempotent state sync that models Proxmox VE and UniFi int...
Coding
NetBox API
Try itUse when scripting or batch-driving a NetBox instance over the REST API — relocating/renaming devices, cables, IP assignment, OOB/primary IP, pre-verify + read-back.
What it does
Use when scripting or batch-driving a NetBox instance over the REST API — relocating/renaming devices, cables, IP assignment, OOB/primary IP, pre-verify + read-back.
The skill document
NetBox REST API (using the instance, not administering it)
Operate an existing NetBox through its REST API — batch device moves, renames, cable sweeps, IP assignment. For upgrading the instance, venv drift, rq worker crashes, or reverse-proxy/CSRF config, use the sibling skill netbox-upgrade instead — this skill is only about using NetBox via the API.
Verified against NetBox 4.6.8 (Authorization: Bearer on every call). Never hardcode the token in files — reference it as a placeholder. Access may be IP-restricted at the reverse proxy; if every call 403s with a valid token, that's an allowlist/egress issue, not auth.
Golden rules for API work
- PRE-VERIFY ids → names with GET before any write batch. Build the plan from a fresh fetch, match every planned object by name, abort on any mismatch. Never PATCH by id from a plan written before the fetch.
- READ BACK after every batch. Re-fetch and diff against the source document (rack, position, site, name, description). A
200is not proof the change landed. - Batch writes = one Python script, not N tool calls. A single loop (GET-verify → PATCH/DELETE → read-back) survives timeouts and gives an auditable per-object log. Reusable CLI:
scripts/netbox_api.py. - Wrong field names are SILENT no-ops, not errors — see
positionbelow.
The field gotchas (these cost real debugging time)
- The U position field is
position(float), NOTrack_unit. NetBox 4.x usesposition+faceon the Device API. Sendingrack_unitreturns 200 and changes nothing — always read back. - OOB IP is a device field, not an IP flag. The GUI checkbox "Make this the out-of-band IP for the device" maps to
Device.oob_ip(nullableBriefIPAddress). There is no boolean on theIPAddressschema. Set it viaPATCH /api/dcim/devices/{id}/with{"oob_ip": }. primary_ip4is nullable. A device whose only IP is its OOB/management IP should normally have no primary IP:PATCH /api/dcim/devices/{id}/ {"primary_ip4": null}(200) keepsoob_ipintact. Use this when the IP is "only" the management/OOB IP.- A device's
primary_ip4cannot be unassigned — 400Cannot reassign IP address while it is designated as the primary IP. Clear the device'sprimary_ip4first. - The
assigned_objectbrief in GET responses has NOtypekey — onlyid/url/display/device/name(±cable/_occupied). To map IPs → devices, match"/dcim/interfaces/" in assigned_object["url"]and readassigned_object["device"]["id"]. Keying ontype == "dcim.interface"matches nothing.
Common operations
- Relocate a device to a rack:
PATCH /api/dcim/devices/{id}/with{"rack": , "position": }(add"face": "front"when it matters). For a cross-site move, ALSO send"site": {"id": }and"location": nullin the same call, or you'll get 400s:Rack X does not belong to site YLocation Z does not belong to site YGet `` fromGET /api/dcim/racks/?limit=100(name→id, note each rack'ssite).PATCHis a partial update — send only the fields you're changing.
- Assign an IP to an interface:
PATCH /api/ipam/ip-addresses/{id}/with{"assigned_object_type": "dcim.interface", "assigned_object_id": }(theassigned_objectobject field itself is read-only). To unassign, sendnullfor both — but it 400s if the IP is a device'sprimary_ip4; clear that first. - Management-IP migration pattern (move + OOB + unassign legacy): one device PATCH sets
oob_ipandprimary_ip4to the new IP, then a second PATCH unassigns the legacy IP object. Doing the unassign first 400s while it's still primary. - Verify IP assignments via the IP object (
assigned_object_id), not the interface list view — the interfaceip_address/ip_addressesfields can come back empty/missing even when assigned. - Trim payloads: append
&fields=name,rack,position,siteto a list endpoint. Note nested objects come back as{"id":…, "name":…}(a dict — use.get("id")). - Object counts:
GET /api///?limit=1then read.count— cheap way to see if a collection is populated.
Pagination (a real trap)
- List responses carry
count,next,previous. Always follownext— never assume the last page. GET .../?limit=2000silently caps at 1000 (serverMAX_PAGE_SIZEdefault). Follownextto get the rest.?name=exact-stringon/api/dcim/devices/uses exact-match (iexact) semantics and may return 0 hits for a name that exists with different casing/form — fetch the full list and filter client-side.scripts/netbox_api.pyfollowsnextcorrectly; use itsallsubcommand rather than hand-paging.
Discovery
- OpenAPI schema:
GET /api/schema/— served as YAML, not JSON. Parse withyaml.safe_load, notjson.load. - Version/status:
GET /api/status/→ JSON withnetbox-version,django-version,python-version,plugins,rq-workers-running. - No "Docs" app in 4.6.x:
/api/docs/*all 404. When a user says "change the documentation" they usually mean thedescription/commentsfields on existing objects (or relocating objects) — ask which objects.
Verify after a batch
Re-fetch the affected objects and diff against the source document. Spot-check a known device's rack/position/name and an assigned IP's assigned_object_id. Don't close on 200s alone.
Verifying API claims against upstream (no live box needed)
When you can't reach the running instance, check the tagged source instead: raw.githubusercontent.com/netbox-community/netbox/v/netbox/....
- 4.6.x serializer layout is a
serializers_/package —serializers.pyatdcim/api/andipam/api/only re-export from it. Device fields:dcim/api/serializers_/devices.py; nested (brief) forms:dcim/api/serializers_/nested.py; IP fields:ipam/api/serializers_/ip.py. - Pagination defaults:
netbox/netbox/config/parameters.py—PAGINATE_COUNTdefault 50,MAX_PAGE_SIZEdefault 1000 (both overridable inconfiguration.py). - Device validation error strings:
dcim/models/devices.py; primary-IP unassign guard:ipam/models/ip.py.
References
references/netbox-api.md— field-level quick reference (all the field gotchas + common ops in one table-ish doc)references/batch-device-operations.md— full verified recipe: relocate 21 nodes, renames, description tags, 68-cable sweep (GET-verify → write → read-back)
Sibling skill
netbox-upgrade— major-version upgrades, venv/requirements drift, rq workerImportError: cannot import name 'Connection', and reverse-proxy CSRF 403s (references/proxy-csrf-403-behind-reverse-proxy.md). If the task is fixing the instance rather than driving it via the API, switch there.
Related skills
Routine NetBox, Zabbix, and homelab maintenance with responsible change authority.
Use this skill whenever the user needs to operate a network device — read device facts, interfaces (+ counters/IP), BGP/LLDP neighbors (summary and detail), ARP/MAC tables, VLANs, routes, hardware environment (fans/temp/power/CPU/mem), optics, NTP, users, SNMP info, VRFs, and an aggregated device-health summary; run read-only RCA diagnostics on interface health and BGP neighbors; back up a switch/router config, diff a candidate config (dry-run), and merge/replace/rollback config — across Cisco IOS/IOS-XE, Nexus NX-OS, IOS-XR, Arista EOS, and Juniper Junos via NAPALM. An optional NetBox block adds source-of-truth lookups. Always use this skill for "back up switch config", "show bgp neighbors", "diff network config", "push config to router", "show interfaces on the switch", or tasks mentioning "cisco", "arista", "juniper", "nexus", "ios-xr", or "napalm". Do NOT use when the target is not a NAPALM-supported network device (Kubernetes clusters, hypervisor VMs, and cloud consoles are out of
Combined network + gateway status with VPN info, IP location, and system health
NetHunt (nethunt.com). Use this skill for ANY NetHunt request — reading, creating, updating, and deleting data. Whenever a task involves NetHunt, use this sk...
Read and manage Netlify sites, deploys, builds, DNS, and env vars via an OAuth-backed API gateway.