mcpField
N
netbox-api-integration

netbox-api-integration

github.com/netboxlabsCloud & DevOps2156 installsUpdated 2w ago
Repository

About netbox-api-integration

A skill for developers integrating with NetBox, covering REST and GraphQL authentication, queries, bulk operations, performance, and data modeling. It also highlights version-specific API behavior.

Key features

  1. 01REST and GraphQL query patterns
  2. 02Token authentication and version notes
  3. 03Pagination and bulk-write patterns
  4. 04Filtering, concurrency, and performance
  5. 05NetBox object dependencies and modeling

Use cases

  1. 01Writing NetBox API integration code
  2. 02Paginating queries over large datasets
  3. 03Creating or updating objects in bulk

MCP support

Describes using a configured NetBox MCP server or NetBox Platform MCP to inspect a live instance and verify fields, filters, and object state.

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/netboxlabs/skills --skill netbox-api-integration

After installing, type/netbox-api-integrationin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
authentication.md
branching-patterns.md
data-modeling.md
diode-integration.md
graphql-patterns.md
performance.md
rest-api-patterns.md

Details

From the project README

NetBox API Integration

Patterns and practices for integrating with NetBox REST and GraphQL APIs. Covers authentication, querying, bulk operations, performance optimization, data modeling, and integration tooling.

Target: NetBox 4.5–4.7 (current: 4.7.3). v2 tokens require 4.5+; REST cursor pagination, ETag/If-Match, and add_tags/remove_tags require 4.6+; ?background=true, per-object bulk errors, and {value, label} selection custom fields are 4.7+. See Version Notes. Scope: API integration only — not plugin development, custom scripts, or NetBox administration.

Your knowledge of NetBox APIs may be outdated. Pagination behavior, filtering expressions, token formats, and GraphQL features change between releases. Prefer retrieval over pre-trained knowledge for specific API details.

Retrieval Sources

SourceURL / MethodUse for
NetBox REST API docshttps://netboxlabs.com/docs/netbox/integrations/rest-api/Endpoints, filtering, pagination
NetBox GraphQL docshttps://netboxlabs.com/docs/netbox/integrations/graphql-api/Query syntax, schema
pynetbox repohttps://github.com/netbox-community/pynetboxSDK methods, changelog
NetBox MCP serverIf configured — query live instance to verify object schemas and current stateField names, available filters, object existence
NetBox Platform MCPIf configured — full CRUD verification against live NetBox Cloud/EnterpriseEnd-to-end validation

FIRST: Verify Connectivity

Before writing integration code, confirm your NetBox instance is reachable and your token is valid:

curl -s -H "Authorization: Bearer $NETBOX_TOKEN" "$NETBOX_URL/api/status/" | python -m json.tool

You should see a JSON response with netbox-version. If you get 403, your token lacks permissions. If connection refused, check the URL.


Authentication

Use v2 tokens on NetBox 4.5+. v1 tokens are deprecated as of 4.6 and will be removed in 5.0.

# v2 token (recommended)
headers = {"Authorization": "Bearer nbt_abc123.xxxxxxxxxxxxxxxx"}

# v1 token (legacy — deprecated 4.6, removed 5.0; migrate before 5.0)
headers = {"Authorization": "Token 0123456789abcdef01234567"}

v2 tokens require API_TOKEN_PEPPERS in NetBox server config. Use the provisioning endpoint (POST /api/users/tokens/provision/) for automated token creation. The plaintext is the token field of the creation/provision response (key is only the public identifier) and is returned only once (4.6.1+) — capture it immediately; only a hash is stored thereafter.

NetBox 4.7+: token is read-only on create — the server always generates the plaintext and ignores a client-supplied value. Never create tokens with ?background=true: 4.7.0/4.7.1 recorded the plaintext in the job result (readable by anyone with core.view_job); 4.7.2 rejects it with 400. Run ≥ 4.7.2 and rotate any token created that way. Running a custom script via POST /api/extras/scripts/<id>/ now requires a token with write enabled.

See references/authentication.md for token migration, IP restrictions, and provisioning details.

REST API Essentials

Always Paginate
def get_all(api_url, endpoint, headers, limit=100):
    results, url = [], f"{api_url}/{endpoint}/?limit={limit}"
    while url:
        data = requests.get(url, headers=headers).json()
        results.extend(data["results"])
        url = data.get("next")
    return results

NetBox 4.6+ — cursor pagination. For large datasets, pass ?start=<pk>&limit=<n> instead of offset. Returns objects with id >= start, ordered by PK. Iterate by setting the next start to the last result's id + 1; follow next until null. Gotchas: start and offset together → 400; count is always null in cursor mode; previous is always null (forward-only). Use offset pagination when you need a total count or backward navigation.

Use PATCH, Not PUT

PATCH updates only specified fields. PUT replaces the entire object — omitted fields may be cleared.

Concurrency & Tags (NetBox 4.6+)
  • Optimistic concurrency (ETag / If-Match). Detail-view responses (GET/POST/PATCH/PUT on a single object) return a weak ETag header. Send it back on a later PATCH/PUT via If-Match: <etag>; if the object changed meanwhile, the server rejects with 412 Precondition Failed and returns the current ETag. If-Match: * just asserts the object exists. Omitting the header keeps last-write-wins behavior. Use it to guard against lost updates in multi-writer integrations.
  • Partial tag edits (add_tags / remove_tags). Write-only fields that add or remove specific tags without replacing the whole tags set — concurrency-safe when multiple clients each own a subset of tags. Constraints: cannot be combined with tags in the same request; remove_tags is update-only (not on create); the same tag can't appear in both lists.
Bulk Operations Use List Endpoints

No separate bulk endpoints. POST/PATCH/DELETE a JSON array to the list endpoint. All bulk ops are atomic (all succeed or all fail).

# Bulk create
requests.post(f"{API_URL}/dcim/devices/", headers=headers, json=[device1, device2])

# Bulk update (each object must include "id")
requests.patch(f"{API_URL}/dcim/devices/", headers=headers, json=[{"id": 1, "status": "active"}])

# Bulk delete
requests.delete(f"{API_URL}/dcim/devices/", headers=headers, json=[{"id": 1}, {"id": 2}])

NetBox 4.7+: a failed bulk write returns {"detail": "...", "errors": [...]} with one entry per failing object — {"index": N, "errors": {...}} for creates, {"id": N, "errors": {...}} for updates/deletes — so fix only those objects and resubmit (the batch is still rolled back). Add ?background=true to run a large bulk write as a job: you get 202 with {"job": {"id", "url", "status"}}, validation is deferred to the worker, and you must poll job.url and check data.status_code. See references/rest-api-patterns.md.

Critical Performance Rules
  1. Trim config_context on device/VM lists. On 4.5–4.6 it is rendered per object on every request — always send ?omit=config_context (4.5.2+; ?exclude=config_context is equivalent there) for a 10-100x speedup. On 4.7.0–4.7.2 it is pre-rendered, cached, and always loaded — ?fields=, ?brief=True, or ?omit=config_context only shrink the payload. On 4.7.3+ those same parameters again skip the DB annotation and the cached column, so trimming matters for performance (less than on 4.6, but real). ?exclude= is a silent no-op on every 4.7.x — use ?omit=config_context as the version-portable form
  2. Use ?brief=True for dropdowns and reference lists (~90% smaller responses)
  3. Use ?fields= for specific field selection
  4. Avoid ?q= search filter at scale — use specific filters like name__ic=
Filtering

Use lookup expressions for server-side filtering:

ExpressionExampleDescription
__icname__ic=coreContains (case-insensitive)
__iswname__isw=sw-Starts with
__gte/__ltevid__gte=100Numeric range
__isnullprimary_ip4__isnull=falseNull check
__nstatus__n=offlineNot equal
cf_ prefixcf_environment=productionCustom field filter

See references/rest-api-patterns.md for complete REST API reference including filtering expressions, field selection, ordering, nested serializers, OPTIONS discovery, and error handling.

GraphQL Essentials

Every List Query Must Be Paginated
query {
  device_list(pagination: {limit: 100, offset: 0}) {
    name
    status
    site { name }
  }
}
Paginate at Every Nesting Level
# Nested lists must also have limits
site_list(pagination: {limit: 10}) {
  devices(pagination: {limit: 50}) {
    interfaces(pagination: {limit: 100}) { name }
  }
}
Key Rules
  • Use the query optimizer — essential for production. Scores reduced from 20,500 → 17 in real cases.
  • Request only needed fields — don't over-fetch
  • Keep depth ≤ 3, never exceed 5
  • Filter server-side using filters: parameter
  • Use ID filters over name filters for performance (avoids JOINs)
  • Calibrate optimizer against production data for accurate scores

NetBox 4.5+: Use local filter fields (e.g., site on interface_list) instead of deeply nested filter paths.

NetBox 4.5.2+: Cursor-based pagination is GA. Pass pagination: {start: N, limit: M} — returns records with id >= start, ordered by PK. Set the next page's start to the last record's id + 1. (Omit start and it falls back to offset pagination.) This supersedes the older filters: {id__gte: N} deep-pagination workaround, which is only needed pre-4.5.2.

NetBox 4.7+: selection/multi-selection values inside custom_fields resolve to {value, label} objects (same as REST). Service filters are flat and list-valued (port_mappings, protocol, port, port__gt/gte/lt/lte), and ServiceProtocolEnum members are TCP/UDP/SCTP (no ROLE_ prefix). See references/graphql-patterns.md.

GraphQL pagination defaults (differ from REST):

  • Omitting pagination entirely returns all matching records.
  • pagination without limit returns Strawberry Django's default of 100 (not the REST default of 50).
  • pagination: {limit: 0} returns zero records — the opposite of REST ?limit=0 (which returns all).
  • MAX_PAGE_SIZE (default 1000) caps limit. NetBox 4.6.1+ also enforces GRAPHQL_MAX_QUERY_DEPTH server-side, so overly deep queries can be hard-rejected, not just slow.
GraphQL vs REST Decision
Use CaseUse
Single object CRUDREST
Bulk create/update/deleteREST (GraphQL is read-only)
Related data in one requestGraphQL
CI/CD scriptsREST
Flexible reportingGraphQL

See references/graphql-patterns.md for complete GraphQL reference including pagination strategies, depth management, fan-out avoidance, complexity budgets, and the query optimizer.

Performance

The most impactful optimizations:

OptimizationImpact
Trim config_context from lists (?omit=config_context, 4.5.2+; ?exclude= works on 4.5–4.6 only and is ignored on all 4.7.x)10-100x faster on 4.5–4.6; payload only on 4.7.0–4.7.2 (cached); skips the DB annotation again on 4.7.3+
Use ?brief=True for lists~90% smaller responses
Avoid ?q= at scaleOrders of magnitude faster
Parallelize independent requestsLinear speedup
Use specific filters over generic searchIndex-optimized queries

See references/performance.md for async pagination, caching strategies, parallel request patterns, and infrastructure considerations.

Data Modeling

Dependency Order

Objects must be created in order: Organization → Sites → DCIM Prerequisites → Racks → Devices → IPAM → Virtualization → Circuits → Connections.

A device needs its device_type, role, and site to exist first. Use Diode to skip manual ordering.

Key Hierarchies
  • Site: Region → Site Group → Site → Location (recursive) → Rack → Device
  • IPAM: RIR → Aggregate → Prefix (hierarchical) → IP Address; VRFs scope prefixes/IPs
Natural Keys, Custom Fields, Tags, Tenants
  • Query by name/slug instead of numeric IDs for readable code
  • Custom fields use cf_ prefix for filtering. (4.7) Selection/multi-selection values are read as {"value": "prod", "label": "Production"} — compare cf["x"]["value"], and write the raw value (echoing the object back fails validation)
  • Tags enable cross-object-type classification
  • Tenants provide logical resource separation

See references/data-modeling.md for the complete dependency order, hierarchy diagrams, and patterns for custom fields, tags, and tenant isolation.

Integration Tooling

pynetbox (Python Client)

Current release: 7.9.0 (2026-10-08; first release to declare NetBox 4.7 support; Python 3.12+ required). Use ≥ 7.9.0 against 4.7: it flattens {value, label} selection custom fields and sends only the changed custom fields on update, so saves of objects with multi-selection custom fields succeed (7.8.0 can fail them). 7.9.0 also accepts changelog_message= on create, save(), update() and delete() (bulk included) and compares more list fields (object_types, permissions, groups, route targets, config-context scopes) as sets, so reordering no longer triggers a PATCH.

import pynetbox
nb = pynetbox.api("https://netbox.example.com", token="nbt_abc123.xxxxxxxxxxxxxxxx",
                  pagination="cursor")  # 7.8+: keyset paging on NetBox 4.6+; falls back to offset on older servers

devices = nb.dcim.devices.filter(site="nyc-dc1", status="active")  # Auto-paginated
device = nb.dcim.devices.get(name="switch-01")
device.status = "planned"
device.save()  # Uses PATCH
  • pagination="cursor" is sequential (ignores threading=True), cannot be combined with ordering, and len(record_set) costs an extra count request.
  • extensions=[BranchingExtension] / [CustomObjectsExtension] from pynetbox.extensions (7.8+) add typed records and plugin actions — see Branching.
  • On NetBox 4.7, record.custom_fields["x"] for a selection field is a {"value", "label"} dict.
Diode (Data Ingestion)

For bulk ingestion, use Diode instead of direct API. Specify dependencies by name — Diode resolves or creates them automatically. No dependency ordering needed.

from netboxlabs.diode.sdk import DiodeClient
from netboxlabs.diode.sdk.ingester import Device, Entity

with DiodeClient(target="grpc://diode.example.com:8080/diode",
                 app_name="discovery", app_version="1.0.0") as client:
    device = Device(name="switch-01", device_type="Catalyst 9300",
                    manufacturer="Cisco", site="NYC-DC1", role="Access Switch")
    client.ingest([Entity(device=device)])

Use Diode for: network discovery, bulk migrations, scripts creating many related objects. Use REST/GraphQL for: reading data, single object CRUD, complex queries.

See references/diode-integration.md for bulk ingestion, nested objects, dry-run mode, authentication, and supported entity types.

Webhooks

Configure NetBox to push changes via webhooks. Always verify the X-Hook-Signature header (HMAC-SHA512). Events include created, updated, deleted with full object data and before/after snapshots.

Change Tracking

Query extras.object_changes for audit trails — includes timestamp, action, user, and before/after data.

Branching (Plugin)

Requires the netbox-branching plugin — v1.2.x for NetBox 4.7, v1.1.x for NetBox 4.4–4.6. Plugin setup and workflows: netbox-branching.

Lifecycle: Create → Wait (PROVISIONING→READY) → Work → Sync → Merge

  • Context header: X-NetBox-Branch: {schema_id} — use the 8-char schema_id, not name or numeric ID
  • Async operations: sync/merge/revert return Job objects — poll job["url"] until status == "completed"
  • Dry-run: All async ops accept {"commit": false} for validation
  • pynetbox 7.8+: pynetbox.api(..., extensions=[BranchingExtension]) gives branch.sync()/merge()/revert() (return Job records; 409 with conflicts in exc.error until acknowledged) and with nb.activate_branch(branch): sets the header for you

See references/branching-patterns.md for the complete branch lifecycle, context header usage, async job polling, and session wrapper patterns.

Version Notes

NetBox 4.7.3 (2026-10-13)
  • ?omit=config_context, ?fields= (without it), or ?brief=True on device/VM list/detail reads skip the config-context DB annotation and cached column again (#23288; ?exclude= stays a no-op), and ?background=true bulk writes now carry request headers and the query string into the worker, so X-NetBox-Branch is honoured (#23247 — on 4.7.0–4.7.2 it was dropped and the write landed in main). Run ≥ 4.7.3: 4.7.0/4.7.1 leaked token plaintexts into job results; 4.7.3 adds branch-safe background writes and the config-context omit optimisation.
NetBox 4.7 (2026-09-02; run ≥ 4.7.3)
  • ?exclude=config_context is silently ignored on all 4.7.x; config_context is pre-rendered and cached on devices/VMs. On 4.7.0–4.7.2 it is always loaded (?fields=, ?omit=config_context, ?brief=True trim the payload only); on 4.7.3+ those parameters also skip the DB work.
  • Selection/multi-selection custom fields read as {value, label} (REST + GraphQL); write the raw value.
  • Bulk writes: ?background=true → 202 + job (poll it; 400 with If-Match, 503 with no worker); failed bulk create/update returns per-object errors. Bulk ops stay all-or-none. Inside a branch, X-NetBox-Branch is dropped on 4.7.0–4.7.2 (the write lands in main) — carried on 4.7.3+.
  • Tokens: token read-only on create; /api/users/tokens/ rejects ?background=true (4.7.2 — earlier 4.7.x leaked plaintexts into job results); script execution via REST needs a write-enabled token.
  • Services: protocol/ports → port_mappings (["tcp/53", "udp/53"]); legacy pair deprecated, removed in 5.0; protocol__ic-style and port__empty lookups gone. See references/data-modeling.md.
  • Interface/VMInterface mac_address is writable (creates or updates the primary MAC); MACAddress.is_primary is read-only.
  • New endpoints: dcim/cooling-sources|cooling-feeds|cooling-intakes|cooling-outflows|cooling-intake-templates|cooling-outflow-templates/, dcim/module-bay-types/. New fields: end_of_life (DeviceType/ModuleType), channels/channel_id (Interface), Job.execution_time, Webhook.timeout, CustomField.status/nulls_first.
  • Upgrade prerequisites (PostgreSQL 15+, ltree, Redis 6+) and the long MPTT→ltree migration are covered in netbox-administration.
NetBox 4.6
  • REST cursor pagination (?start=), ETag/If-Match, add_tags/remove_tags; v1 tokens deprecated (removed 5.0); plaintext token returned once (4.6.1+); GRAPHQL_MAX_QUERY_DEPTH enforced server-side (4.6.1+); cluster optional on VMs.

External References

Comments

Markdown supported