Introduction
Network automation replaces CLI-driven manual operations with programmatic control. APIs are the interface layer that makes this possible — they let applications configure devices, stream telemetry, enforce policies, and provision services without human intervention.
Modern routers, switches, and firewalls expose REST and gRPC endpoints. Cloud networking platforms like AWS VPC and Azure Virtual Network offer SDK-backed APIs. Automation frameworks like Nornir and Ansible use these APIs to orchestrate at scale. Understanding network API development is essential for any engineer working with modern infrastructure.
This guide covers the major network API styles (REST, gRPC, NETCONF, gNMI), Python client and server implementations, automation libraries, API security, and design best practices for 2026.
Understanding Network APIs
What Are Network APIs?
Network APIs provide programmatic access to network resources. They enable applications to interact with network devices, services, and infrastructure.
Network APIs serve several purposes: device configuration, status monitoring, policy management, and service provisioning.
API Types
Network APIs fall into four major categories. Each targets different use cases — configuration management, telemetry, or general programmability.
| API Style | Transport | Data Format | Primary Use Case |
|---|---|---|---|
| REST | HTTP/1.1 or HTTP/2 | JSON | Device config, monitoring, cloud APIs |
| gRPC | HTTP/2 | Protocol Buffers (binary) | Service-to-service, high-throughput data |
| NETCONF | SSH or TLS | XML | Transactional device configuration |
| gNMI | gRPC (HTTP/2) | Protocol Buffers or JSON | Streaming telemetry, model-driven config |
flowchart LR
subgraph Northbound["Northbound APIs"]
REST
gRPC
end
subgraph Southbound["Southbound Protocols"]
NETCONF
gNMI
CLI_SSH["CLI (SSH)"]
end
NMS["NMS / Controller"] --> REST
NMS --> gRPC
NMS --> NETCONF
NMS --> gNMI
NMS --> CLI_SSH
REST --> Devices["Network Devices<br/>(Routers, Switches, Firewalls)"]
gRPC --> Devices
NETCONF --> Devices
gNMI --> Devices
CLI_SSH --> Devices
REST dominates northbound APIs (controllers, cloud platforms, orchestrators) where ease of integration matters most. gRPC powers internal microservice communication and high-throughput data pipelines. NETCONF and gNMI operate as southbound protocols for direct device management — NETCONF excels at transactional configuration with rollback, while gNMI provides real-time streaming telemetry that replaces legacy SNMP polling.
REST API Development
REST Fundamentals
REST (Representational State Transfer) is the dominant API style for network programmability — over 83% of public APIs use REST in 2026. It uses HTTP methods mapped to CRUD operations: GET (retrieve), POST (create), PUT (replace), PATCH (partial update), DELETE (remove). Resources are identified by URLs, and JSON is the standard data exchange format.
Network devices from Cisco, Juniper, Arista, and cloud providers all expose REST APIs. A GET request to /api/v1/interfaces/GigabitEthernet0/1 returns the interface state as JSON. A PATCH to the same URL updates specific fields.
REST API Design
Good REST API design follows consistent conventions that reduce cognitive load for consumers.
Use plural nouns for collection endpoints (/interfaces, /vlans, /devices). Use HTTP methods for actions — don’t embed verbs in URLs (POST /interfaces not POST /createInterface). Version from day one with a URL prefix (/api/v1/) to avoid breaking consumers when the schema evolves.
Return meaningful HTTP status codes: 200 OK for success, 201 Created for new resources, 400 Bad Request for invalid input, 401 Unauthorized for missing auth, 404 Not Found for missing resources, 429 Too Many Requests for rate limits, 500 Internal Server Error for server faults.
Document every endpoint with the OpenAPI specification (formerly Swagger). An OpenAPI spec serves as a contract between the API provider and consumer, enables auto-generated client SDKs, and powers interactive documentation like Swagger UI.
Python REST API Client Example
import requests
import json
class NetworkDeviceAPI:
def __init__(self, base_url, username, password):
self.base_url = base_url
self.auth = (username, password)
def get_interfaces(self):
response = requests.get(
f"{self.base_url}/api/v1/interfaces",
auth=self.auth
)
response.raise_for_status()
return response.json()
def get_interface(self, name):
response = requests.get(
f"{self.base_url}/api/v1/interfaces/{name}",
auth=self.auth
)
response.raise_for_status()
return response.json()
def update_interface(self, name, config):
response = requests.put(
f"{self.base_url}/api/v1/interfaces/{name}",
auth=self.auth,
json=config
)
response.raise_for_status()
return response.json()
def create_vlan(self, vlan_id, name):
response = requests.post(
f"{self.base_url}/api/v1/vlans",
auth=self.auth,
json={"id": vlan_id, "name": name}
)
response.raise_for_status()
return response.json()
Using Flask to Create Network API
from flask import Flask, request, jsonify
from functools import wraps
app = Flask(__name__)
# Simple in-memory database
devices = {}
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or auth.username != 'admin' or auth.password != 'secret':
return jsonify({"error": "Unauthorized"}), 401
return f(*args, **kwargs)
return decorated
@app.route('/api/v1/devices', methods=['GET'])
@require_auth
def list_devices():
return jsonify(devices)
@app.route('/api/v1/devices/<device_id>', methods=['GET'])
@require_auth
def get_device(device_id):
device = devices.get(device_id)
if device:
return jsonify(device)
return jsonify({"error": "Device not found"}), 404
@app.route('/api/v1/devices', methods=['POST'])
@require_auth
def add_device():
data = request.json
device_id = data.get('id')
devices[device_id] = data
return jsonify(data), 201
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
gRPC for Networking
What Is gRPC?
gRPC is a high-performance RPC framework originally developed by Google. It uses HTTP/2 for multiplexed transport and Protocol Buffers (Protobuf) for binary serialization. Compared to REST+JSON, gRPC delivers 2-10x lower latency and up to 60% smaller payloads — critical for high-throughput network telemetry and microservice communication.
gRPC supports four communication patterns:
- Unary RPC: single request, single response (like a REST call)
- Server streaming: client sends one request, server streams multiple responses
- Client streaming: client streams multiple requests, server sends one response
- Bidirectional streaming: both sides send independent streams simultaneously
For networking, bidirectional streaming is the most powerful pattern — a collector can subscribe to telemetry from a device and receive continuous updates over a single persistent connection.
Protocol Buffers Definition
Protocol Buffers (proto) define data structures and service interfaces.
syntax = "proto3";
message Interface {
string name = 1;
string ip_address = 2;
string status = 3;
int32 speed = 4;
}
message InterfaceList {
repeated Interface interfaces = 1;
}
message GetInterfaceRequest {
string name = 1;
}
message UpdateInterfaceRequest {
string name = 1;
Interface interface = 2;
}
service NetworkService {
rpc GetInterface(GetInterfaceRequest) returns (Interface);
rpc ListInterfaces(Empty) returns (InterfaceList);
rpc UpdateInterface(UpdateInterfaceRequest) returns (Interface);
rpc StreamInterfaceStats(Interface) returns (stream InterfaceStats);
}
Python gRPC Server
import grpc
from concurrent import futures
import network_pb2
import network_pb2_grpc
class NetworkService(network_pb2_grpc.NetworkServiceServicer):
def __init__(self):
self.interfaces = {}
def GetInterface(self, request, context):
name = request.name
if name in self.interfaces:
return network_pb2.Interface(**self.interfaces[name])
context.set_code(grpc.StatusCode.NOT_FOUND)
context.set_details('Interface not found')
return network_pb2.Interface()
def ListInterfaces(self, request, context):
return network_pb2.InterfaceList(
interfaces=[
network_pb2.Interface(**iface)
for iface in self.interfaces.values()
]
)
def UpdateInterface(self, request, context):
self.interfaces[request.name] = {
'name': request.name,
'ip_address': request.interface.ip_address,
'status': request.interface.status,
'speed': request.interface.speed
}
return network_pb2.Interface(**self.interfaces[request.name])
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
network_pb2_grpc.add_NetworkServiceServicer_to_server(
NetworkService(), server
)
server.add_insecure_port('[::]:50051')
server.start()
server.wait_for_termination()
if __name__ == '__main__':
serve()
Python gRPC Client
import grpc
import network_pb2
import network_pb2_grpc
def run():
channel = grpc.insecure_channel('localhost:50051')
stub = network_pb2_grpc.NetworkServiceStub(channel)
# List interfaces
response = stub.ListInterfaces(network_pb2.Empty())
print("Interfaces:")
for iface in response.interfaces:
print(f" {iface.name}: {iface.ip_address} ({iface.status})")
# Get specific interface
try:
iface = stub.GetInterface(
network_pb2.GetInterfaceRequest(name="eth0")
)
print(f"\neth0: {iface.ip_address}")
except grpc.RpcError as e:
print(f"Error: {e.details()}")
if __name__ == '__main__':
run()
gNMI — Streaming Telemetry and Configuration
gNMI (gRPC Network Management Interface) is a modern protocol for model-driven network management. It uses gRPC for transport, Protocol Buffers or JSON for serialization, and YANG data models (typically OpenConfig) to define the data structure. Unlike SNMP’s polling model, gNMI supports push-based streaming telemetry through its Subscribe RPC.
How gNMI Compares to SNMP
| Feature | SNMP | gNMI |
|---|---|---|
| Transport | UDP (mostly) | HTTP/2 (TCP) |
| Data model | MIB files (ASN.1) | YANG / OpenConfig |
| Data format | ASN.1 BER | Protocol Buffers or JSON |
| Telemetry model | Polling (pull) | Subscription (push) |
| Latency | 30s–5min intervals | Near real-time |
| Security | Community strings / USM | TLS mutual auth |
| Configuration | Limited | Set RPC for config |
gNMI Operations
gNMI defines four RPCs:
- Capabilities: discover which YANG models the device supports
- Get: retrieve a single snapshot of state or config
- Set: modify configuration (replace, update, delete)
- Subscribe: stream telemetry data on value changes or at intervals
The Subscribe RPC is the key differentiator — you set the subscription once, and the device pushes updates only when values change. This eliminates the polling overhead of SNMP.
Python gNMI Subscription Example
import gnmi_pb2
import gnmi_pb2_grpc
import grpc
# Establish a secure gNMI channel
channel = grpc.secure_channel(
"router.example.com:9339",
grpc.ssl_channel_credentials()
)
stub = gnmi_pb2_grpc.gNMIStub(channel)
# Subscribe to interface counter updates
sub_request = gnmi_pb2.SubscribeRequest(
subscribe=gnmi_pb2.SubscriptionList(
prefix=gnmi_pb2.Path(
origin="openconfig",
elem=[gnmi_pb2.PathElem(name="interfaces")]
),
mode=gnmi_pb2.SubscriptionList.STREAM,
subscription=[
gnmi_pb2.Subscription(
path=gnmi_pb2.Path(
elem=[
gnmi_pb2.PathElem(name="interface"),
gnmi_pb2.PathElem(name="...", key={"name": "*"}),
gnmi_pb2.PathElem(name="state"),
gnmi_pb2.PathElem(name="counters"),
gnmi_pb2.PathElem(name="in-octets"),
]
),
mode=gnmi_pb2.Subscription.SAMPLE,
sample_interval=10000000000, # 10 seconds
)
],
)
)
for response in stub.Subscribe(iter([sub_request])):
for update in response.update.update:
print(f"Path: {update.path}")
print(f"Value: {update.val}")
In production, gNMI telemetry flows into time-series databases (Prometheus via gNMIc or Telegraf) and visualization dashboards (Grafana). Major vendors — Cisco IOS-XR, Juniper JunOS, Arista EOS, Nokia SR OS — support gNMI across enterprise and service provider platforms.
Network Automation Libraries
Nornir — Python Automation Framework
Nornir is a pure-Python automation framework designed for network tasks. Unlike Ansible (which uses YAML playbooks), Nornir lets you write automation logic directly in Python — giving you full control over conditionals, loops, error handling, and result processing.
from nornir import InitNornir
from nornir_netmiko import netmiko_send_command
from nornir_utils.plugins.functions import print_result
# Initialize Nornir with an inventory (hosts.yaml)
nr = InitNornir(config_file="config.yaml")
# Run a command on all devices in parallel
result = nr.run(
task=netmiko_send_command,
command_string="show ip interface brief"
)
print_result(result)
Nornir runs tasks in parallel across devices using a configurable thread pool. The inventory file defines device groups, credentials, and platform types:
# hosts.yaml
core-router:
hostname: 10.0.1.1
username: admin
password: secure_password
platform: cisco_ios
groups:
- routers
access-switch-1:
hostname: 10.0.2.1
username: admin
password: secure_password
platform: arista_eos
groups:
- switches
Netmiko — SSH-Based Device Access
Netmiko is the most widely used Python library for SSH-based network automation. It handles device prompt detection, privilege escalation, command paging, and configuration mode — abstracting away the quirks of each vendor’s CLI:
from netmiko import ConnectHandler
device = {
"device_type": "cisco_ios",
"host": "router.example.com",
"username": "admin",
"password": "secure_password",
}
with ConnectHandler(**device) as conn:
conn.enable()
output = conn.send_command("show interfaces")
print(output)
config_commands = [
"interface GigabitEthernet0/1",
"description uplink-to-core",
"ip address 192.168.1.1 255.255.255.0",
"no shutdown",
]
conn.send_config_set(config_commands)
conn.save_config()
Netmiko supports 80+ device platforms including Cisco IOS-XE, NX-OS, Arista EOS, Juniper JunOS, Huawei, and MikroTik.
NAPALM — Multi-Vendor Abstraction
NAPALM provides a unified API across different network operating systems. It abstracts vendor-specific syntax behind common methods like get_facts(), get_interfaces(), compare_config(), and commit_config():
from napalm import get_network_driver
driver = get_network_driver("ios")
device = driver(
hostname="router.example.com",
username="admin",
password="secure_password",
)
with device:
facts = device.get_facts()
print(f"Model: {facts['model']}")
print(f"Serial: {facts['serial_number']}")
print(f"OS Version: {facts['os_version']}")
# Compare candidate config before applying
device.load_replace_candidate(filename="new-config.cfg")
diff = device.compare_config()
if diff:
print(diff)
device.commit_config()
else:
device.discard_config()
NETCONF — Structured Device Configuration
NETCONF uses XML-encoded RPCs over SSH for transactional configuration management. Each operation — get-config, edit-config, commit, rollback — maps to a well-defined RPC. Devices validate configurations against YANG models before applying, catching errors before they break the network.
from ncclient import manager
with manager.connect(
host="router.example.com",
port=830,
username="admin",
password="secure_password",
hostkey_verify=False,
device_params={"name": "csr"},
) as m:
# Retrieve running configuration as structured XML
config = m.get_config(source="running")
print(config.data_xml)
# Apply a configuration change with candidate + commit
new_config = """
<config>
<interfaces xmlns="urn:ietf:params:xml:ns:yang:ietf-interfaces">
<interface>
<name>GigabitEthernet0/1</name>
<description>uplink-to-core</description>
<enabled>true</enabled>
<ipv4 xmlns="urn:ietf:params:xml:ns:yang:ietf-ip">
<address>
<ip>192.168.1.1</ip>
<netmask>255.255.255.0</netmask>
</address>
</ipv4>
</interface>
</interfaces>
</config>
"""
m.edit_config(target="candidate", config=new_config)
m.commit()
API Design Best Practices
Pagination
Never return unbounded lists. Any endpoint that returns a collection of resources must support pagination. Use cursor-based pagination (not offset) for data that grows over time — cursors remain stable even when records are inserted or deleted.
# Response with cursor-based pagination
{
"data": [...],
"pagination": {
"next_cursor": "eyJpZCI6IDUwMH0=",
"has_more": true
}
}
Include total_count, next_cursor, and has_more in every paginated response. Retrofit pagination is painful and breaks existing consumers.
Idempotency
POST operations that create resources should accept an Idempotency-Key header. If the client sends the same key with the same request body, the server returns the original result without creating a duplicate resource. This is critical for payment processing, device configuration, and any operation where network retries are common.
import hashlib
import hmac
# Client generates a unique idempotency key per request
idempotency_key = hashlib.sha256(
f"{method}:{path}:{body}".encode()
).hexdigest()
headers = {"Idempotency-Key": idempotency_key}
response = requests.post(url, json=body, headers=headers)
Consistent Error Responses
Every error response should include a machine-readable code, a human-readable message, and a link to documentation. Use standard HTTP status codes correctly — never return 200 for errors.
{
"error": {
"code": "INVALID_INPUT",
"message": "Interface name must be 1-64 characters",
"docs": "https://api.example.com/docs/errors#invalid_input"
}
}
Rate Limiting
Protect APIs from abuse and ensure fair use. Return standard rate limit headers on every response so clients can implement backoff strategies:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 842
X-RateLimit-Reset: 1623456789
Retry-After: 45
When a client exceeds the limit, return 429 Too Many Requests with a Retry-After header.
API Versioning
APIs evolve. Fields are added, deprecated, and restructured. The question is not whether you need versioning — it is which strategy causes the least pain for consumers.
- URL path versioning (
/api/v1/interfaces): simple, explicit, easy to route. Best for public APIs. - Header-based versioning (
Accept: application/vnd.api.v1+json): clean URLs but harder to test. Best for internal APIs. - Additive-only changes: add fields and endpoints without removing or changing existing ones. Give consumers 6-12 months to migrate off deprecated features. This is the least disruptive approach.
Network API Security
API security is not optional. In 2026, APIs are the most common attack vector for data breaches. Every network API must implement authentication, authorization, encryption, and audit logging.
Authentication Methods
| Method | Security Level | Best For | Implementation Effort |
|---|---|---|---|
| API Keys | Basic | Server-to-server, internal tools | Low |
| OAuth 2.0 + JWT | High | User-facing APIs, SaaS platforms | Medium |
| OAuth 2.0 + PKCE | Very High | SPAs, mobile apps (public clients) | Medium |
| mTLS | Very High | Service-to-service, zero-trust networks | High |
OAuth2 for Network APIs
OAuth2 with JWT tokens provides secure, scoped access to network resources. The client obtains a short-lived access token (15 minutes typical) from an authorization server, then includes it in API requests as a Bearer token:
import requests
# Obtain access token via client credentials grant
token_url = "https://auth.example.com/oauth/token"
response = requests.post(
token_url,
data={
"grant_type": "client_credentials",
"client_id": "network-client",
"client_secret": "client-secret-here",
},
)
token = response.json()["access_token"]
# Use the token for API calls
headers = {"Authorization": f"Bearer {token}"}
response = requests.get(
"https://api.example.com/network/devices",
headers=headers,
)
API Keys
API keys are simpler but less granular. Use them for internal automation scripts and server-to-server communication where OAuth2 would be over-engineered:
headers = {"X-API-Key": "your-api-key-here"}
response = requests.get(
"https://api.example.com/network/vlans",
headers=headers,
)
mTLS for Zero-Trust Networks
Mutual TLS (mTLS) requires both the client and server to present valid certificates. This is the strongest authentication method for service-to-service communication and is increasingly used in zero-trust network architectures:
import requests
# Client presents its certificate for mTLS handshake
response = requests.get(
"https://api.network.internal:443/devices",
cert=("/path/to/client.crt", "/path/to/client.key"),
verify="/path/to/ca.pem",
)
Security Checklist
- HTTPS everywhere — no HTTP fallback, enable HSTS headers
- Input validation — validate types, lengths, formats, and ranges on every endpoint
- Short-lived tokens — access tokens expire in 15 minutes; use refresh token rotation
- Audit logging — log who accessed what and when; never log credentials or tokens
- CORS restrictions — restrict allowed origins to verified frontend domains
Resources
- OpenAPI Specification — Standard for REST API documentation
- gRPC Documentation — Official gRPC guides and API reference
- gNMI Specification (OpenConfig) — gRPC Network Management Interface spec
- Nornir Documentation — Python automation framework for networking
- Netmiko Documentation — Multi-vendor SSH library for Python
- NAPALM Documentation — Network automation abstraction layer
- OpenConfig YANG Models — Vendor-neutral device data models
- ncclient (NETCONF Python Client) — Python library for NETCONF
Comments