Files
2025-11-29 18:00:21 +08:00

1093 lines
22 KiB
Markdown

# NetBox REST API Guide
**NetBox Version:** 4.3.0
**API Documentation:** <https://netboxlabs.com/docs/netbox/en/stable/>
Complete reference for working with the NetBox REST API, including authentication, common operations, filtering, pagination, and error handling patterns for the Virgo-Core infrastructure.
---
## Table of Contents
- [Quick Start](#quick-start)
- [Authentication](#authentication)
- [API Endpoints Structure](#api-endpoints-structure)
- [Common Operations](#common-operations)
- [Filtering and Search](#filtering-and-search)
- [Pagination](#pagination)
- [Bulk Operations](#bulk-operations)
- [Error Handling](#error-handling)
- [Rate Limiting](#rate-limiting)
- [Python Client (pynetbox)](#python-client-pynetbox)
- [Best Practices](#best-practices)
- [Security](#security)
---
## Quick Start
### Using curl
```bash
# Get all sites
curl -H "Authorization: Token YOUR_API_TOKEN" \
https://netbox.spaceships.work/api/dcim/sites/
# Create a new IP address
curl -X POST \
-H "Authorization: Token YOUR_API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"address": "192.168.3.10/24",
"dns_name": "docker-01-nexus.spaceships.work",
"status": "active",
"tags": ["production-dns", "terraform"]
}' \
https://netbox.spaceships.work/api/ipam/ip-addresses/
```
### Using pynetbox (Python)
```python
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["pynetbox>=7.0.0", "infisical-python>=2.3.3"]
# ///
import pynetbox
from infisical import InfisicalClient
# Get token from Infisical (following Virgo-Core security pattern)
client = InfisicalClient()
token = client.get_secret(
secret_name="NETBOX_API_TOKEN",
project_id="7b832220-24c0-45bc-a5f1-ce9794a31259",
environment="prod",
path="/matrix"
).secret_value
# Connect to NetBox
nb = pynetbox.api('https://netbox.spaceships.work', token=token)
# Query sites
sites = nb.dcim.sites.all()
for site in sites:
print(f"{site.name}: {site.description}")
```
See [../tools/netbox_api_client.py](../tools/netbox_api_client.py) for complete working example.
---
## Authentication
### Token Authentication
NetBox uses token-based authentication for API access.
#### Creating a Token
1. Log into NetBox web UI
2. Navigate to **User****API Tokens**
3. Click **Add Token**
4. Configure permissions (read, write)
5. Copy token (only shown once)
#### Storing Tokens Securely
**❌ NEVER hardcode tokens:**
```python
# DON'T DO THIS
token = "a1b2c3d4e5f6..." # NEVER hardcode!
```
**✅ Use Infisical (Virgo-Core standard):**
```python
from infisical import InfisicalClient
def get_netbox_token() -> str:
"""Get NetBox API token from Infisical."""
client = InfisicalClient()
secret = client.get_secret(
secret_name="NETBOX_API_TOKEN",
project_id="7b832220-24c0-45bc-a5f1-ce9794a31259",
environment="prod",
path="/matrix"
)
return secret.secret_value
```
See [netbox-best-practices.md](netbox-best-practices.md#security) for complete security patterns.
#### Using Tokens
**With curl:**
```bash
curl -H "Authorization: Token YOUR_TOKEN" \
https://netbox.spaceships.work/api/dcim/sites/
```
**With pynetbox:**
```python
import pynetbox
nb = pynetbox.api('https://netbox.spaceships.work', token='YOUR_TOKEN')
```
### Session Authentication
Session authentication is available when using the NetBox web interface (browser-based). Not recommended for API automation.
---
## API Endpoints Structure
All API endpoints are prefixed with `/api/` followed by the app name:
| Prefix | Purpose | Example Endpoints |
|--------|---------|-------------------|
| `/api/dcim/` | Data Center Infrastructure Management | sites, racks, devices, cables |
| `/api/ipam/` | IP Address Management | ip-addresses, prefixes, vlans, vrfs |
| `/api/virtualization/` | Virtual Machines | virtual-machines, clusters |
| `/api/circuits/` | Circuit Management | circuits, providers |
| `/api/tenancy/` | Multi-tenancy | tenants, tenant-groups |
| `/api/extras/` | Additional Features | tags, custom-fields, webhooks |
### Common Endpoint Patterns
All endpoints follow REST conventions:
```text
GET /api/{app}/{model}/ # List all objects
POST /api/{app}/{model}/ # Create new object(s)
GET /api/{app}/{model}/{id}/ # Get specific object
PUT /api/{app}/{model}/{id}/ # Full update
PATCH /api/{app}/{model}/{id}/ # Partial update
DELETE /api/{app}/{model}/{id}/ # Delete object
```
---
## Common Operations
### Sites
**List all sites:**
```bash
GET /api/dcim/sites/
```
```python
sites = nb.dcim.sites.all()
```
**Create a site:**
```bash
POST /api/dcim/sites/
{
"name": "Matrix Cluster",
"slug": "matrix",
"status": "active",
"description": "3-node Proxmox cluster",
"region": null,
"tags": ["proxmox", "production"]
}
```
```python
site = nb.dcim.sites.create(
name="Matrix Cluster",
slug="matrix",
status="active",
description="3-node Proxmox cluster",
tags=[{"name": "proxmox"}, {"name": "production"}]
)
```
**Get specific site:**
```bash
GET /api/dcim/sites/{id}/
GET /api/dcim/sites/?slug=matrix
```
```python
site = nb.dcim.sites.get(slug='matrix')
```
**Update a site:**
```bash
PATCH /api/dcim/sites/{id}/
{
"description": "Updated description"
}
```
```python
site.description = "Updated description"
site.save()
```
**Delete a site:**
```bash
DELETE /api/dcim/sites/{id}/
```
```python
site.delete()
```
### Devices
**List devices:**
```bash
GET /api/dcim/devices/
GET /api/dcim/devices/?site=matrix
```
```python
devices = nb.dcim.devices.filter(site='matrix')
```
**Create a device:**
```bash
POST /api/dcim/devices/
{
"name": "foxtrot",
"device_type": 1,
"site": 1,
"device_role": 3,
"status": "active",
"tags": ["proxmox-node"]
}
```
```python
device = nb.dcim.devices.create(
name="foxtrot",
device_type=1,
site=nb.dcim.sites.get(slug='matrix').id,
device_role=3,
status="active",
tags=[{"name": "proxmox-node"}]
)
```
**Get device with related objects:**
```bash
GET /api/dcim/devices/{id}/?include=interfaces,config_context
```
```python
device = nb.dcim.devices.get(name='foxtrot')
interfaces = device.interfaces # Related interfaces
```
### IP Addresses
**List IP addresses:**
```bash
GET /api/ipam/ip-addresses/
GET /api/ipam/ip-addresses/?vrf=management
```
```python
ips = nb.ipam.ip_addresses.all()
ips_mgmt = nb.ipam.ip_addresses.filter(vrf='management')
```
**Create IP address with DNS:**
```bash
POST /api/ipam/ip-addresses/
{
"address": "192.168.3.10/24",
"dns_name": "docker-01-nexus.spaceships.work",
"status": "active",
"description": "Docker host for Nexus registry",
"tags": ["production-dns", "terraform"]
}
```
```python
ip = nb.ipam.ip_addresses.create(
address="192.168.3.10/24",
dns_name="docker-01-nexus.spaceships.work",
status="active",
description="Docker host for Nexus registry",
tags=[{"name": "production-dns"}, {"name": "terraform"}]
)
```
**Assign IP to interface:**
```python
ip = nb.ipam.ip_addresses.get(address='192.168.3.10/24')
interface = nb.dcim.interfaces.get(device='foxtrot', name='eth0')
ip.assigned_object_type = 'dcim.interface'
ip.assigned_object_id = interface.id
ip.save()
```
**Get IP with assigned device:**
```bash
GET /api/ipam/ip-addresses/{id}/
```
```python
ip = nb.ipam.ip_addresses.get(address='192.168.3.10/24')
if ip.assigned_object:
print(f"Assigned to: {ip.assigned_object.device.name}")
```
### Prefixes
**List prefixes:**
```bash
GET /api/ipam/prefixes/
GET /api/ipam/prefixes/?site=matrix
```
```python
prefixes = nb.ipam.prefixes.filter(site='matrix')
```
**Get available IPs in prefix:**
```bash
GET /api/ipam/prefixes/{id}/available-ips/
```
```python
prefix = nb.ipam.prefixes.get(prefix='192.168.3.0/24')
available = prefix.available_ips.list()
print(f"Available IPs: {len(available)}")
```
**Create IP from available pool:**
```bash
POST /api/ipam/prefixes/{id}/available-ips/
{
"dns_name": "k8s-01-worker.spaceships.work",
"tags": ["production-dns"]
}
```
```python
prefix = nb.ipam.prefixes.get(prefix='192.168.3.0/24')
ip = prefix.available_ips.create(
dns_name="k8s-01-worker.spaceships.work",
tags=[{"name": "production-dns"}]
)
```
### Virtual Machines
**List VMs:**
```bash
GET /api/virtualization/virtual-machines/
GET /api/virtualization/virtual-machines/?cluster=matrix
```
```python
vms = nb.virtualization.virtual_machines.filter(cluster='matrix')
```
**Create VM:**
```bash
POST /api/virtualization/virtual-machines/
{
"name": "docker-01",
"cluster": 1,
"role": 2,
"vcpus": 4,
"memory": 8192,
"disk": 100,
"status": "active",
"tags": ["docker", "production"]
}
```
```python
vm = nb.virtualization.virtual_machines.create(
name="docker-01",
cluster=nb.virtualization.clusters.get(name='matrix').id,
vcpus=4,
memory=8192,
disk=100,
status="active",
tags=[{"name": "docker"}, {"name": "production"}]
)
```
**Create VM interface:**
```python
vm_interface = nb.virtualization.interfaces.create(
virtual_machine=vm.id,
name='eth0',
type='virtual',
enabled=True
)
```
**Assign IP to VM interface:**
```python
ip = nb.ipam.ip_addresses.create(
address='192.168.3.10/24',
dns_name='docker-01-nexus.spaceships.work',
assigned_object_type='virtualization.vminterface',
assigned_object_id=vm_interface.id,
tags=[{"name": "production-dns"}]
)
# Set as primary IP
vm.primary_ip4 = ip.id
vm.save()
```
---
## Filtering and Search
### Query Parameters
Most endpoints support filtering via query parameters:
```bash
# Filter by single value
GET /api/dcim/devices/?site=matrix
# Filter by multiple values (OR logic)
GET /api/dcim/devices/?site=matrix&site=backup
# Exclude values
GET /api/dcim/devices/?site__n=decommissioned
# Partial matching (case-insensitive)
GET /api/dcim/devices/?name__ic=docker
# Starts with
GET /api/dcim/devices/?name__isw=k8s
# Ends with
GET /api/dcim/devices/?name__iew=worker
# Greater than / less than
GET /api/ipam/prefixes/?prefix_length__gte=24
# Empty / not empty
GET /api/dcim/devices/?tenant__isnull=true
```
### Python Filtering
```python
# Single filter
devices = nb.dcim.devices.filter(site='matrix')
# Multiple filters (AND logic)
devices = nb.dcim.devices.filter(site='matrix', status='active')
# Partial matching
devices = nb.dcim.devices.filter(name__ic='docker')
# Greater than
prefixes = nb.ipam.prefixes.filter(prefix_length__gte=24)
# Get single object
device = nb.dcim.devices.get(name='foxtrot')
```
### Full-Text Search
```bash
# Search across all fields
GET /api/dcim/devices/?q=foxtrot
```
```python
results = nb.dcim.devices.filter(q='foxtrot')
```
### Tag Filtering
```bash
# Devices with specific tag
GET /api/dcim/devices/?tag=proxmox-node
# Multiple tags (OR logic)
GET /api/dcim/devices/?tag=proxmox-node&tag=ceph-node
```
```python
devices = nb.dcim.devices.filter(tag='proxmox-node')
```
---
## Pagination
API responses are paginated by default (50 results per page).
### Response Format
```json
{
"count": 1000,
"next": "https://netbox.spaceships.work/api/dcim/devices/?limit=50&offset=50",
"previous": null,
"results": [...]
}
```
### Controlling Pagination
```bash
# Custom page size (max 1000)
GET /api/dcim/devices/?limit=100
# Skip to offset
GET /api/dcim/devices/?limit=50&offset=100
```
### Python Pagination
```python
# Get all (handles pagination automatically)
all_devices = nb.dcim.devices.all()
# Custom page size
devices = nb.dcim.devices.filter(limit=100)
# Manual pagination
page1 = nb.dcim.devices.filter(limit=50, offset=0)
page2 = nb.dcim.devices.filter(limit=50, offset=50)
```
**⚠️ Warning:** Using `all()` on large datasets can be slow. Use filtering to reduce result set.
---
## Bulk Operations
NetBox supports bulk creation, update, and deletion.
### Bulk Create
**curl:**
```bash
POST /api/dcim/devices/
[
{"name": "device1", "device_type": 1, "site": 1},
{"name": "device2", "device_type": 1, "site": 1},
{"name": "device3", "device_type": 1, "site": 1}
]
```
**pynetbox:**
```python
devices_data = [
{"name": "device1", "device_type": 1, "site": 1},
{"name": "device2", "device_type": 1, "site": 1},
{"name": "device3", "device_type": 1, "site": 1}
]
# Bulk create (more efficient than loop)
devices = nb.dcim.devices.create(devices_data)
```
### Bulk Update
**curl:**
```bash
PUT /api/dcim/devices/
[
{"id": 1, "status": "active"},
{"id": 2, "status": "active"},
{"id": 3, "status": "offline"}
]
```
**pynetbox:**
```python
# Update multiple objects
for device in nb.dcim.devices.filter(site='matrix'):
device.status = 'active'
device.save()
# Or bulk update with PUT
updates = [
{"id": 1, "status": "active"},
{"id": 2, "status": "active"}
]
nb.dcim.devices.update(updates)
```
### Bulk Delete
**curl:**
```bash
DELETE /api/dcim/devices/
[
{"id": 1},
{"id": 2},
{"id": 3}
]
```
**pynetbox:**
```python
# Delete multiple objects
devices_to_delete = nb.dcim.devices.filter(status='decommissioned')
for device in devices_to_delete:
device.delete()
```
---
## Error Handling
### HTTP Status Codes
| Code | Meaning | Description |
|------|---------|-------------|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid data or malformed request |
| 401 | Unauthorized | Missing or invalid authentication |
| 403 | Forbidden | Insufficient permissions |
| 404 | Not Found | Resource not found |
| 500 | Internal Server Error | Server error |
### Error Response Format
```json
{
"detail": "Not found.",
"error": "Object does not exist"
}
```
### Python Error Handling
```python
import pynetbox
from requests.exceptions import HTTPError
try:
device = nb.dcim.devices.get(name='nonexistent')
if not device:
print("Device not found")
except HTTPError as e:
if e.response.status_code == 404:
print("Resource not found")
elif e.response.status_code == 403:
print("Permission denied")
else:
print(f"HTTP Error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
```
### Validation Errors
```python
try:
site = nb.dcim.sites.create(
name="Invalid Site",
slug="invalid slug" # Spaces not allowed
)
except pynetbox.RequestError as e:
print(f"Validation error: {e.error}")
```
### Best Practices
1. **Always check for None:**
```python
device = nb.dcim.devices.get(name='foo')
if device:
print(device.name)
else:
print("Device not found")
```
2. **Use try/except for create/update:**
```python
try:
ip = nb.ipam.ip_addresses.create(address='192.168.1.1/24')
except pynetbox.RequestError as e:
print(f"Failed to create IP: {e.error}")
```
3. **Validate data before API calls:**
```python
import ipaddress
def validate_ip(ip_str: str) -> bool:
try:
ipaddress.ip_interface(ip_str)
return True
except ValueError:
return False
```
See [../tools/netbox_api_client.py](../tools/netbox_api_client.py) for complete error handling examples.
---
## Rate Limiting
NetBox may enforce rate limits on API requests.
### Response Headers
```text
X-RateLimit-Limit: 1000 # Total requests allowed
X-RateLimit-Remaining: 950 # Remaining requests
X-RateLimit-Reset: 1640000000 # Reset time (Unix timestamp)
```
### Handling Rate Limits
```python
import time
from requests.exceptions import HTTPError
def api_call_with_retry(func, max_retries=3):
"""Retry API call if rate limited."""
for attempt in range(max_retries):
try:
return func()
except HTTPError as e:
if e.response.status_code == 429: # Rate limited
retry_after = int(e.response.headers.get('Retry-After', 60))
print(f"Rate limited. Retrying in {retry_after}s...")
time.sleep(retry_after)
else:
raise
raise Exception("Max retries exceeded")
# Usage
result = api_call_with_retry(lambda: nb.dcim.devices.all())
```
### Best Practices
1. **Use pagination** to reduce request count
2. **Cache responses** when data doesn't change frequently
3. **Batch operations** using bulk endpoints
4. **Implement exponential backoff** for retries
5. **Monitor rate limit headers** in production
---
## Python Client (pynetbox)
### Installation
```bash
pip install pynetbox
```
Or with uv (Virgo-Core standard):
```python
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.11"
# dependencies = ["pynetbox>=7.0.0"]
# ///
```
### Basic Usage
```python
import pynetbox
# Connect
nb = pynetbox.api(
'https://netbox.spaceships.work',
token='your-token'
)
# Query
sites = nb.dcim.sites.all()
device = nb.dcim.devices.get(name='foxtrot')
# Create
site = nb.dcim.sites.create(
name='Matrix',
slug='matrix'
)
# Update
device.status = 'active'
device.save()
# Delete
device.delete()
```
### Advanced Patterns
**Lazy loading:**
```python
# Only fetches when accessed
device = nb.dcim.devices.get(name='foxtrot')
interfaces = device.interfaces # API call happens here
```
**Custom fields:**
```python
device.custom_fields['serial_number'] = 'ABC123'
device.save()
```
**Relationships:**
```python
# Get device's primary IP
device = nb.dcim.devices.get(name='foxtrot')
if device.primary_ip4:
print(device.primary_ip4.address)
# Get IP's assigned device
ip = nb.ipam.ip_addresses.get(address='192.168.3.5/24')
if ip.assigned_object:
print(ip.assigned_object.device.name)
```
**Threading (for bulk operations):**
```python
from concurrent.futures import ThreadPoolExecutor
def get_device_info(device_name):
return nb.dcim.devices.get(name=device_name)
device_names = ['foxtrot', 'golf', 'hotel']
with ThreadPoolExecutor(max_workers=5) as executor:
devices = list(executor.map(get_device_info, device_names))
```
---
## Best Practices
### 1. Use Filtering to Reduce Data Transfer
```python
# ❌ Inefficient: Get all devices then filter
all_devices = nb.dcim.devices.all()
matrix_devices = [d for d in all_devices if d.site.slug == 'matrix']
# ✅ Efficient: Filter on server
matrix_devices = nb.dcim.devices.filter(site='matrix')
```
### 2. Use Specific Fields
```bash
# Only get specific fields
GET /api/dcim/devices/?fields=name,status,primary_ip4
```
### 3. Cache Responses
```python
from functools import lru_cache
@lru_cache(maxsize=128)
def get_site(site_slug: str):
"""Cache site lookups."""
return nb.dcim.sites.get(slug=site_slug)
```
### 4. Validate Before API Calls
```python
import ipaddress
import re
def validate_dns_name(name: str) -> bool:
"""Validate DNS naming convention."""
pattern = r'^[a-z0-9-]+-\d{2}-[a-z0-9-]+\.[a-z0-9.-]+$'
return bool(re.match(pattern, name))
def validate_ip(ip_str: str) -> bool:
"""Validate IP address format."""
try:
ipaddress.ip_interface(ip_str)
return True
except ValueError:
return False
# Use before API calls
if validate_dns_name(dns_name) and validate_ip(ip_address):
ip = nb.ipam.ip_addresses.create(
address=ip_address,
dns_name=dns_name
)
```
### 5. Use Bulk Operations
```python
# ❌ Slow: Create in loop
for ip in ip_list:
nb.ipam.ip_addresses.create(address=ip)
# ✅ Fast: Bulk create
nb.ipam.ip_addresses.create([
{"address": ip} for ip in ip_list
])
```
### 6. Implement Proper Error Handling
See [Error Handling](#error-handling) section above.
### 7. Use HTTPS in Production
```python
# ✅ Always use HTTPS
nb = pynetbox.api('https://netbox.spaceships.work', token=token)
# ❌ Never use HTTP in production
nb = pynetbox.api('http://netbox.spaceships.work', token=token)
```
### 8. Rotate Tokens Regularly
Store tokens in Infisical and rotate every 90 days. See [Security](#security) section.
---
## Security
### API Token Security
1. **Store in Infisical (never hardcode):**
```python
from infisical import InfisicalClient
client = InfisicalClient()
token = client.get_secret(
secret_name="NETBOX_API_TOKEN",
project_id="7b832220-24c0-45bc-a5f1-ce9794a31259",
environment="prod",
path="/matrix"
).secret_value
```
2. **Use environment variables (alternative):**
```python
import os
token = os.getenv('NETBOX_API_TOKEN')
if not token:
raise ValueError("NETBOX_API_TOKEN not set")
```
3. **Rotate tokens regularly** (every 90 days)
4. **Use minimal permissions** (read-only for queries, write for automation)
### HTTPS Only
```python
# ✅ Verify SSL certificates
nb = pynetbox.api(
'https://netbox.spaceships.work',
token=token,
ssl_verify=True # Default, but explicit is better
)
# ⚠️ Only disable for dev/testing
nb = pynetbox.api(
'https://netbox.local',
token=token,
ssl_verify=False # Self-signed cert
)
```
### Audit API Usage
Monitor API calls in production:
```python
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
def audit_api_call(action: str, resource: str, details: dict):
"""Log API calls for audit."""
logger.info(f"API Call: {action} {resource} - {details}")
# Example usage
ip = nb.ipam.ip_addresses.create(address='192.168.1.1/24')
audit_api_call('CREATE', 'ip-address', {'address': '192.168.1.1/24'})
```
### Network Security
- Use VPN for remote access to NetBox
- Restrict NetBox API access by IP (firewall rules)
- Use Proxmox VLANs to isolate management traffic
---
## Additional Resources
- **Official API Docs:** <https://netboxlabs.com/docs/netbox/en/stable/>
- **pynetbox Docs:** <https://pynetbox.readthedocs.io/>
- **OpenAPI Schema:** `GET https://netbox.spaceships.work/api/schema/`
- **GraphQL API:** `https://netbox.spaceships.work/graphql/`
## Related Documentation
- [NetBox Data Models](netbox-data-models.md) - Data model relationships
- [NetBox Best Practices](netbox-best-practices.md) - Infrastructure patterns
- [Tools: netbox_api_client.py](../tools/netbox_api_client.py) - Complete working example
- [DNS Naming Conventions](../workflows/naming-conventions.md) - DNS naming rules
---
**Next:** [NetBox Data Models Guide](netbox-data-models.md)