Skip to main content

Network API Development Complete Guide 2026

Created: March 2, 2026 Larry Qu 12 min read

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

Comments

Share this article

Scan to read on mobile

👍 Was this article helpful?