Docs

Quick Start

This guide walks through the basics of using pynetbox to interact with NetBox.

Basic Connection

Import pynetbox and create an API connection:

import pynetbox

nb = pynetbox.api(
    'http://localhost:8000',
    token='d6f4e314a5b5fefd164995169f28ae32d987704f'
)

Connection Parameters

pynetbox.api() accepts the following parameters:

  • url (required): Base URL of your NetBox instance, including scheme (http:// or https://).
  • token (optional): API authentication token. Required for write operations and for endpoints that are not public.
  • threading (optional, default False): Enable multithreaded page fetching for .all() and .filter(). See Threading.
  • strict_filters (optional, default False): Validate filter parameters against NetBox's OpenAPI specification. See Filter Validation.
nb = pynetbox.api(
    'http://localhost:8000',
    token='your-token-here',
    threading=True,
    strict_filters=True,
)

API Structure

pynetbox mirrors NetBox's app structure. Each NetBox app is exposed as an attribute of the API object:

nb.circuits         # Circuit management
nb.core             # Core objects (data sources, jobs, object changes)
nb.dcim             # Data Center Infrastructure Management
nb.extras           # Tags, custom fields, webhooks, etc.
nb.ipam             # IP Address Management
nb.tenancy          # Tenants and contacts
nb.users            # Users and permissions
nb.virtualization   # Virtual machines and clusters
nb.vpn              # VPN tunnels and terminations
nb.wireless         # Wireless LANs and links

Each app exposes endpoints that map to NetBox API endpoints. Dashes in endpoint names are converted to underscores:

nb.dcim.devices
nb.dcim.sites
nb.dcim.racks
nb.ipam.ip_addresses   # /api/ipam/ip-addresses/
nb.ipam.prefixes

Plugin Endpoints

Endpoints provided by NetBox plugins are accessible under the plugins namespace. As with built-in endpoints, dashes in plugin or endpoint names are converted to underscores:

# /api/plugins/my-plugin/objects/
nb.plugins.my_plugin.objects.all()

Querying Data

Getting All Objects

Use .all() to retrieve every object from an endpoint:

for device in nb.dcim.devices.all():
    print(device.name)

RecordSet is a one-shot iterator

.all() and .filter() return a RecordSet, which can only be iterated once. To iterate the results multiple times, materialize them with list():

devices = list(nb.dcim.devices.all())

Filtering Objects

Use .filter() to query objects matching one or more criteria. Keyword arguments correspond to filter parameters supported by the NetBox endpoint:

# All devices with a specific role
leaf_switches = nb.dcim.devices.filter(role='leaf-switch')

# Multiple filters (AND)
devices = nb.dcim.devices.filter(
    site='headquarters',
    status='active',
    role='access-switch',
)

# Filter on a custom field
devices = nb.dcim.devices.filter(cf_environment='production')

# Freeform search (passed as ?q=...)
results = nb.dcim.devices.filter('rack1')

Invalid filters do not raise an error by default

NetBox silently ignores unrecognized filter parameters, which means a typo can quietly return the entire table. Enable strict filter validation to catch these errors early.

Getting a Single Object

Use .get() to retrieve a single object. It returns None if no match is found and raises ValueError if more than one record matches:

# By ID
device = nb.dcim.devices.get(1)

# By a unique field
device = nb.dcim.devices.get(name='spine1')

# Combining filters when a single field is not unique
location = nb.dcim.locations.get(site='site-1', name='Row 1')

# get() returns None if no match is found
device = nb.dcim.devices.get(name='nonexistent')
if device is None:
    print("Device not found")

Counting Objects

Use .count() to retrieve only the count of matching objects without fetching them:

total = nb.dcim.devices.count()
active = nb.dcim.devices.count(status='active')

Working with Records

Accessing Attributes

API fields are exposed as attributes on Record objects:

device = nb.dcim.devices.get(1)
print(device.name)
print(device.serial)
print(device.device_type.model)   # Nested object
print(device.site.name)

Inspecting Available Attributes

device = nb.dcim.devices.get(1)

# As a dict
print(dict(device))

# Or get the data structure ready for an API payload
print(device.serialize())

Creating Objects

Use .create() to create new objects. Pass field values as keyword arguments:

# Create a site
new_site = nb.dcim.sites.create(
    name='new-datacenter',
    slug='new-datacenter',
    status='planned',
)

# Create a device (NetBox 3.6+ uses `role`; pre-3.6 used `device_role`)
new_device = nb.dcim.devices.create(
    name='new-switch',
    device_type=1,
    site=new_site.id,
    role=5,
)

# Create an IP address with assignment to an interface
new_ip = nb.ipam.ip_addresses.create(
    address='10.0.0.1/24',
    status='active',
    assigned_object_type='dcim.interface',
    assigned_object_id=123,
)

To create multiple objects in a single request, pass a list of dicts:

nb.dcim.sites.create([
    {'name': 'site-1', 'slug': 'site-1'},
    {'name': 'site-2', 'slug': 'site-2'},
])

Updating Objects

There are two ways to update a single object.

Modify attributes and save()

device = nb.dcim.devices.get(1)
device.serial = 'ABC123'
device.asset_tag = 'ASSET001'
device.save()

save() sends a PATCH containing only the fields that have changed and returns True if the request succeeded.

Pass a dict to update()

device = nb.dcim.devices.get(1)
device.update({
    'serial': 'ABC123',
    'asset_tag': 'ASSET001',
})

Bulk Updates

To update multiple records in a single request, call .update() on the endpoint with a list of records or dicts. Each dict must include an id:

devices = list(nb.dcim.devices.filter(site='test1'))
for device in devices:
    device.status = 'active'
nb.dcim.devices.update(devices)

RecordSet also supports a chainable form:

nb.dcim.devices.filter(site_id=1).update(status='active')

Deleting Objects

Use .delete() to remove an object:

device = nb.dcim.devices.get(1)
device.delete()

To delete multiple objects in a single request, pass a list (or RecordSet) of records or IDs to the endpoint's .delete():

nb.dcim.devices.delete([1, 2, 3])

# Or, delete every record matching a filter:
nb.dcim.devices.filter(status='offline').delete()

Changelog Messages

NetBox 4.4+ can attach a message to the changelog entry created by a write. Pass changelog_message to any create, update, or delete call:

# Create
nb.dcim.sites.create(name='Site A', slug='site-a', changelog_message='Ticket #4137')

# Update a single record
device.serial = 'ABC123'
device.save(changelog_message='Serial from asset audit')
device.update({'status': 'offline'}, changelog_message='Failed PSU')

# Bulk update
nb.dcim.devices.update(devices, changelog_message='Site go-live')
nb.dcim.devices.filter(site_id=1).update(status='active', changelog_message='Site go-live')

# Delete a single record, or in bulk
device.delete(changelog_message='Decommissioned')
nb.dcim.devices.delete([1, 2, 3], changelog_message='Site decommissioned')
nb.dcim.devices.filter(status='offline').delete(changelog_message='Cleanup')

For a bulk create, include changelog_message in each dict of the list. A message is sent only with a request: save() makes no request, and so records no message, when nothing has changed.

Working with Choices

Use .choices() to retrieve the valid values for each choice field on an endpoint. The return value is a dict keyed by field name, with each value being a list of {value, label} entries:

choices = nb.dcim.devices.choices()
for choice in choices['status']:
    print(choice['value'], choice['label'])
# active     Active
# planned    Planned
# staged     Staged
# ...

Pagination

NetBox paginates list responses, but pynetbox fetches subsequent pages automatically as you iterate the result set. You can override the page size with limit:

# Fetch 100 records per page (instead of the server default)
devices = nb.dcim.devices.filter(limit=100)

# Start at a specific offset (requires a positive limit)
devices = nb.dcim.devices.filter(limit=100, offset=200)

Error Handling

pynetbox raises specific exceptions for different failure modes:

import pynetbox

try:
    nb.dcim.devices.create(
        name='test-device',
        device_type=1,
        site=1,
        role=1,
    )
except pynetbox.RequestError as e:
    # HTTP error from NetBox (4xx/5xx)
    print(f"Request failed: {e.error}")
except pynetbox.AllocationError as e:
    # No room left in an available-ips / available-prefixes pool
    print(f"Allocation failed: {e}")
except pynetbox.ContentError as e:
    # Server returned a non-JSON response (usually a misconfigured URL)
    print(f"Content error: {e}")
except pynetbox.ParameterValidationError as e:
    # strict_filters=True and a filter parameter is invalid
    print(f"Invalid filter: {e}")

Next Steps

On this page