Typed API¶
netbox_sdk ships a versioned typed client alongside the raw client and async
facade.
Use typed_api() when you want:
- request payload validation before HTTP
- response payload validation after HTTP
- editor and type-checker support for endpoint methods and models
- explicit NetBox version selection
Entry point¶
from netbox_sdk import typed_api
nb = typed_api(
"https://netbox.example.com",
token="your-token",
netbox_version="4.5",
)
Supported release lines:
4.64.54.44.3
Patch versions normalize to their release line, so 4.4.10 selects the 4.4
typed client.
Continuous integration exercises the live-NetBox suite against
v4.6.6, v4.6.3, v4.6.2, and v4.5.10.
Example¶
import asyncio
from netbox_sdk import typed_api
async def main() -> None:
nb = typed_api(
"https://netbox.example.com",
token="your-token",
netbox_version="4.5",
)
device = await nb.dcim.devices.get(42)
if device is not None:
print(device.name)
asyncio.run(main())
Validation behavior¶
- Request bodies are validated before HTTP and raise
TypedRequestValidationError - Response bodies are validated after HTTP and raise
TypedResponseValidationError - Unsupported versions raise
UnsupportedNetBoxVersionError
Bulk responses are validated against the shape the server returns¶
NetBox reuses the collection path for bulk operations. Posting a single object returns that object; posting a list commits the batch and returns a list. The upstream OpenAPI document declares only the singular response for that path, so the generated bindings declare only the singular model:
The runtime therefore selects the response model from the shape of the request
payload: a list body is validated as list[Model], a single body as Model.
Without this, a committed batch would raise TypedResponseValidationError
after the server had already applied every object — and the natural reaction,
a retry, would create duplicates.
This applies to POST, PUT (bulk-update) and PATCH (bulk-patch) alike,
because all of them route through one request path. Bulk DELETE answers with a
bodyless 204, so there is nothing to validate and the call returns None.
Background bulk operations (NetBox 4.7)¶
NetBox 4.7 accepts ?background=true on bulk POST/PUT/PATCH/DELETE. The
batch is queued instead of executed inline, and the response is 202 carrying a
job reference rather than the committed objects — the feature exists to avoid
proxy timeouts on large batches.
result = await api.dcim.sites.create(
body=[{"name": "B1", "slug": "b1"}, {"name": "B2", "slug": "b2"}],
query={"background": True},
)
result.job.id # 4211
result.job.status # "pending"
The runtime selects the response model from the request: an affirmative
background flag yields BackgroundJobReference, and that takes precedence over
body shape, because a queued batch returns a job for either a single object or a
list. Without the flag, nothing changes.
This is an overlay, pending upstream schema support. The pinned 4.7 artifact (
v4.7.0-beta1) does not describe the parameter, so the generator declares it while keeping the committed bundle byte-faithful to upstream. A guard test fails once a refreshed 4.7 schema describesbackgrounditself, so the overlay cannot outlive its reason to exist.
Generated artifacts¶
The repository ships committed OpenAPI bundles, generated Pydantic models, and generated typed endpoint bindings for the supported release lines. Users do not need to run code generation locally.
Relevant modules:
netbox_sdk.models.v4_6netbox_sdk.models.v4_7(preview)netbox_sdk.models.v4_5netbox_sdk.models.v4_4netbox_sdk.models.v4_3netbox_sdk.typed_versions.v4_6netbox_sdk.typed_versions.v4_7(preview)netbox_sdk.typed_versions.v4_5netbox_sdk.typed_versions.v4_4netbox_sdk.typed_versions.v4_3
Choosing between SDK layers¶
- Use
NetBoxApiClientfor raw request control - Use
api()for the async ergonomic facade - Use
typed_api()for versioned Pydantic-validated I/O
NetBox 4.7 (preview) — service port mappings¶
NetBox 4.7 adds port_mappings, letting a service expose several protocols at
once (DNS on both tcp/53 and udp/53). The legacy single-protocol
protocol + ports pair still works on write: upstream's shared serializer
documents that "either format is accepted", translating the legacy pair into
port_mappings when port_mappings is not supplied. Supplying both is accepted
only when they agree; a genuine conflict is rejected as ambiguous.
# Still accepted on 4.7 — translated server-side into port_mappings
api.ipam.services.create({"name": "ssh", "protocol": "tcp", "ports": [22], ...})
# 4.7-native, and the only way to express multiple protocols
api.ipam.services.create({"name": "dns", "port_mappings": ["tcp/53", "udp/53"], ...})
On read, a single-protocol service reports port_mappings and the legacy
protocol/ports; a multi-protocol service reports null for both, because it
cannot be expressed in the old format.
Generation note.
drf-spectacularomitsprotocolfrom the writable service models'propertiesblock even though the documented write contract accepts it.netbox-sdkrestores it with a deterministic generation overlay (scripts/generate_typed_sdk.py::apply_write_compat_overlay) applied in memory only — the committed OpenAPI bundle stays byte-faithful to the pinned upstream artifact. Without it, a PATCH of{"protocol": "udp", "ports": [53]}would be sent as{"ports": [53]}and NetBox would backfill the stored protocol, silently ignoring the change.