Skip to content

Facade API

netbox_sdk now includes an async convenience layer for common NetBox workflows.

It is feature-equivalent to the familiar PyNetBox-style navigation model, but it is implemented on top of the existing async NetBoxApiClient and schema-driven SDK internals. It is not a synchronous clone of pynetbox.


Entry point

Use api() to create an Api object:

import asyncio
from netbox_sdk import api


async def main():
    nb = api("https://netbox.example.com", token="my-token")
    version = await nb.version()
    print(version)


asyncio.run(main())

Top-level apps are exposed as attributes:

nb.dcim
nb.ipam
nb.virtualization
nb.plugins

CRUD and filtering

Retrieve a single object:

device = await nb.dcim.devices.get(42)
if device is not None:
    print(device.name)

Filter a collection:

devices = nb.dcim.devices.filter(site="lon", role="leaf-switch")

async for device in devices:
    print(device.name)

Count matching rows:

count = await nb.dcim.devices.count(site="lon")
print(count)

Create:

device = await nb.dcim.devices.create(
    name="sw-lon-01",
    site={"id": 1},
    role={"id": 2},
    device_type={"id": 3},
)
print(device.id)

Bulk update:

result = await nb.dcim.devices.update(
    [
        {"id": 10, "name": "sw-lon-10"},
        {"id": 11, "name": "sw-lon-11"},
    ]
)

Bulk delete:

await nb.dcim.devices.delete([10, 11, 12])

Record helpers

Objects returned by the facade are lightweight records with endpoint context.

Refresh full details:

device = await nb.dcim.devices.get(42)
await device.full_details()
print(device.serial)

Save only changed fields:

device.name = "sw-lon-01-renamed"
print(device.updates())  # {"name": "sw-lon-01-renamed"}
await device.save()

Delete a single record:

await device.delete()

Detail endpoints

The facade exposes common NetBox subresources directly from records.

Available IPs and prefixes:

prefix = await nb.ipam.prefixes.get(123)

available_ips = await prefix.available_ips.list()
new_ip = await prefix.available_ips.create({})

new_prefix = await prefix.available_prefixes.create({"prefix_length": 28})

Rack elevation:

rack = await nb.dcim.racks.get(5)

units = await rack.elevation.list()
svg = await rack.elevation.list(render="svg")

NAPALM and render-config:

device = await nb.dcim.devices.get(42)
facts = await device.napalm.list(method="get_facts")
rendered = await device.render_config.create()

Trace and path helpers

Traceable records expose trace():

interface = await nb.dcim.interfaces.get(100)
trace = await interface.trace()

Path-aware records expose paths():

termination = await nb.circuits.circuit_terminations.get(7)
paths = await termination.paths()

Plugins and app config

Installed plugin metadata:

plugins = await nb.plugins.installed_plugins()

Plugin resources:

olts = nb.plugins.gpon.olts.filter(status="active")
async for olt in olts:
    print(olt.name)

Per-app config:

user_config = await nb.users.config()

Branch activation

Branch-scoped requests can be made with activate_branch():

branch = type("Branch", (), {"schema_id": "feature-x"})()

with nb.activate_branch(branch):
    device = await nb.dcim.devices.get(42)

This sets X-NetBox-Branch on requests made within the context.


Strict filters

Enable OpenAPI-backed filter validation at API construction time:

nb = api(
    "https://netbox.example.com",
    token="my-token",
    strict_filters=True,
)

Or override per query:

devices = nb.dcim.devices.filter(site="lon", strict_filters=True)

Unknown filters raise ParameterValidationError.


Notes

  • The facade is async-first. Use it inside async def functions.
  • It reuses the lower-level client; NetBoxApiClient remains available when you want direct request control.
  • The implementation is intentionally explicit. It does not try to reproduce PyNetBox's synchronous or implicit lazy-fetch behavior exactly.