Mantra Networking Mantra Networking

Automating API Workflows with Postman, Python Requests, & Jinja2

Automating API Workflows with Postman, Python Requests, & Jinja2
Created By: Chase Woodard

Table of Contents

  • Introduction
  • Tools Overview
  • Core Concepts & Components
  • Setting Up Your Environment
  • Designing and Testing API Workflows in Postman
  • Exporting Postman Requests to Python
  • Jinja2: Dynamic Payload Templating
  • Integrating Python Requests and Jinja2
  • Automating API Calls
  • Example: End-to-End Workflow
  • Conclusion

Automating API Workflows with Postman, Python Requests, and Jinja2

What Is This Approach?

Automating API workflows with PostmanPython requests, and Jinja2 means building a streamlined, testable process to design, verify, and run network or IT infrastructure tasks—like device provisioning or configuration—using APIs. This method combines:

  • Postman for interactive design, testing, and documentation of API calls in a visual environment.
  • Python requests for executing API calls in scripts that can be automated or integrated into larger workflows.
  • Jinja2 templating to dynamically generate API payloads, letting you inject variables and logic into configurations or operational data.

Together, these tools make the process of automation smoother, more reliable, and easy to maintain.

Why You Need to Know About It

  • Repeatability & Efficiency: Eliminates manual configuration steps in network or infrastructure management by making API tasks scriptable and reusable.
  • Speed & Accuracy: Reduces human error and increases the speed at which you can roll out changes or new services.
  • Flexibility: Jinja2 allows you to easily adapt API payloads for different environments or use cases without rewriting code.
  • Collaboration: Postman enables teams to share workflows, tests, and documentation, making it easier for teams to work together and onboard new engineers.
  • Professional Growth: Mastering these tools lets you leverage automation in complex environments, a must-have skill for modern network engineers and DevOps teams.

How It Works

  1. Design and Test APIs in Postman
    • Use Postman’s GUI to build, validate, and document API calls.
    • Create collections that represent workflows (like onboarding a device or updating firewall rules).
    • Save working examples that serve as your source of truth.
  1. Export Working Calls to Python
    • Postman’s code generation tools allow you to convert any request to Python code.
    • Use these snippets as the base for your automation scripts, ensuring accuracy.
  1. Build Dynamic Payloads with Jinja2
    • Instead of hard-coding API body data, use Jinja2 templates.
    • Templating means one script can work for many scenarios (different devices, users, or configurations) just by switching out variables.
  1. Manage Variables Separately
    • Store data (like IP addresses, credentials, or device lists) in files such as YAML or JSON.
    • This promotes security and version control, and tailoring to each deployment is as simple as changing a variable file.
  1. Script Execution with Python Requests
    • Read your variables, render the payload template, and send the request using Python requests.
    • Loop through tasks for batch operations, integrate error handling, and add logging or reporting as needed.

Tools Overview

This section introduces the foundational tools used to automate API workflows—enabling agile, scalable, and repeatable network automation processes. Understanding each component sets the stage for designing seamless integrations and efficient automation pipelines.

  • Postman Overview:
    • What it is: Postman is a user-friendly platform for building, testing, and documenting APIs. It offers a graphical interface where you can craft and validate HTTP requests, organize them in collections (workflows), and manage authentication and environments.
    • Why it’s important: It provides instant feedback on API behavior, supports team collaboration, and reduces the guesswork from API automation by letting you verify all requests visually before moving to code.
    • How it fits in: The requests you construct in Postman can be exported as Python code snippets and enhanced with templates or automation logic.
  • Python Requests Overview:
    • What it is: Python requests is a popular, easy-to-use Python library for sending HTTP requests—supporting methods like GET, POST, PUT, DELETE, and more with simple syntax.
    • Why it’s important: It allows you to automate API interactions programmatically, making your workflows scalable and repeatable. It’s lightweight, well-documented, and integrates easily with other Python modules.
    • How it fits in: You use requests to execute the API operations outside Postman—whether that’s a one-off script or a loop that automates provisioning across hundreds of devices or users.
  • Jinja2 Overview:
    • What it is: Jinja2 is a flexible, high-performance templating engine for Python. It enables you to define templates with variables, loops, and logic, which are then populated or rendered with external data.
    • Why it’s important: API payloads often require dynamic values—such as device names, IPs, or user attributes. Jinja2 lets you build reusable templates, separating logic from data and avoiding hardcoded configuration files.
    • How it fits in: Jinja2 templates power the dynamic payloads sent in Python requests. You render each payload with variables sourced from files (YAML/JSON), scripts, or other automations.

Core Concepts & Components

This section breaks down the foundational concepts required to effectively build and automate API workflows using Postman, Python, and Jinja2.

  • Workspaces, Collections, and Environments in Postman:
    • Workspaces: They are collaborative spaces in Postman where you organize your projects. You can have personal or team workspaces that isolate different workflows and share across your team.
    • Collections: Collections group related API requests together in a logical structure. They represent workflows or API sets (e.g., all device provisioning calls) and can be nested into folders.
    • Environments: Environments hold variables like URLs, API keys, or tokens specific to different contexts (dev, staging, prod). Using environments supports seamless switching and avoids hard-coding.
    • Variables: Variables can be global, environment, collection, or local — allowing dynamic parameters and reusable requests.
  • Understanding API Authentication and Variables:
    • Authentication Types: Common methods include Bearer tokens, API keys, Basic Auth, OAuth 2.0. Properly securing your API calls requires handling these authentication schemes via headers or parameters.
    • Token Management: Use pre-request scripts in Postman to dynamically generate or refresh tokens, and store them in environment variables for successive API calls.
    • Variable Usage: Variables allow requests to be generic and adaptable. For example, use {{base_url}} and {{auth_token}} instead of hard-coded addresses or credentials.
  • Jinja2 Templating for API Payloads:
    • Dynamic Content Creation: Jinja2 allows templating JSON, XML, or other payload types with placeholders that your Python scripts can replace at runtime with real data.
    • Template Features: Supports variable substitution, loops (for lists/arrays), conditionals, filters, and macros—great for building flexible and reusable API request bodies.
    • Separation of Logic and Data: Using templates plus external variables files (YAML/JSON) keeps your automation scripts clean and maintainable.

Setting Up Your Environment

This section walks you through setting up your development environment to begin automating API workflows with Postman, Python, and Jinja2. Follow the step-by-step instructions below to install each required tool and library.

  • Installing Postman:
    • Step 1: Visit the official Postman website: https://www.postman.com/downloads/
    • Step 2: Choose the installer for your operating system (Windows, macOS, or Linux) and complete the installation process.
    • Step 3: Launch Postman and create a free account (optional, but recommended for collaboration and syncing).
    • Step 4: Familiarize yourself with the interface: Collections, Environments, Workspaces, and the Request Builder.
  • Setting Up Python and Required Libraries:
    • Step 1 – Install Python 3.8 or higher:
    • Step 2 – Set up a virtual environment (recommended):
      python -m venv api-automation-env
      source api-automation-env/bin/activate  # On Windows: api-automation-env\\Scripts\\activate
    • Step 3 – Install required Python libraries:
      pip install requests jinja2 pyyaml

      These libraries provide:

      • requests – for executing API calls via HTTP
      • jinja2 – for creating dynamic API payload templates
      • pyyaml – for loading variables from external YAML files
    • Step 4 – Verify your environment:
      python -c "import requests, jinja2, yaml; print('All libraries installed and available.')" 

Designing and Testing API Workflows in Postman

Postman is not just a tool for testing—it’s a powerful platform for designing complete API workflows. This section walks you through the practical steps of building, variable-tuning, testing, and chaining API requests in Postman as the foundation of your automation pipeline.

  • Creating Collections and Requests:
    • Step 1 – Create a New Collection:
      Click on "Collections" → "New Collection"
      Give your collection a meaningful name (e.g., "Network Device Provisioning"), and add a description.
    • Step 2 – Add Requests to Your Collection:
      Click "Add Request" → Choose Method (GET, POST, PUT, etc.)
      Enter the API endpoint, method, headers, and body as needed. Save each request to the appropriate folder in your collection.
    • Step 3 – Use the Postman Console: The Postman Console (View > Show Postman Console) helps debug requests and view full logs, headers, and response body content.
  • Utilizing Variables and Environments:
    • Step 1 – Create an Environment:
      Click "Environments" → "Add" → Define variables like base_url, token
      Use these variables in your requests via double curly braces, e.g., {{base_url}}.
    • Step 2 – Set Environment: Select your environment from the dropdown next to the workspace name before sending any requests.
    • Step 3 – Use Collection Variables: Define collection-level defaults for values like headers, IDs, or device names to reduce repetition.
  • Scripting with Pre-request and Test Scripts:
    • Pre-request Scripts: Write JavaScript that runs before the request is sent. Common uses:
      • Generate timestamps or UUIDs
      • Set dynamic variables for chaining
      • Attach refreshed tokens automatically
    • Test Scripts: Write tests to validate responses, store session information, or extract data for chaining:
      
      // Example: Save token from response to environment
      let json = pm.response.json();
      pm.environment.set("auth_token", json.token);
  • Chaining Requests and Building Workflows:
    • Step 1 – Use Environment Variables to Chain: Extract values (IDs, tokens, etc.) in one request’s test script and pass them to the next request.
    • Step 2 – Organize Requests in Logical Order: Use the Collection Runner to execute chained requests step-by-step or loop through them with data files.
    • Step 3 – Automate with Collection Runner:
      Click on your collection → Run → Choose environment → Start run
      You can also upload a CSV/JSON data file to iterate over multiple test cases or configurations.

Exporting Postman Requests to Python

After testing and validating your API requests in Postman, the next step is exporting them into Python code using the requests library. This allows you to script and automate those same API calls in your infrastructure or network automation projects.

  • Exporting Code Snippets for Python Requests:
    • Step 1 – Validate the Request in Postman: Ensure your request is working correctly — verify the status code, headers, and body.
    • Step 2 – Open the Code Snippet Generator:
      Click the '' button in the top right of Postman
      This opens Postman's "Generate Code Snippets" feature.
    • Step 3 – Select Python (requests) as the language: Choose "Python — Requests" from the language dropdown.
    • Step 4 – Copy the code: Click "Copy to Clipboard" to grab the generated Python code and paste it into your script or IDE.
  • Understanding the Exported Python Code:
    • The code snippet includes the HTTP method, URL, headers, body (if applicable), and the actual request execution using requests.get(), requests.post(), etc.
    • Here's an example of an exported POST request:
      import requests
      
      url = "https://api.example.com/devices"
      payload = {
          "hostname": "router01",
          "ip_address": "10.1.1.1"
      }
      headers = {
          "Authorization": "Bearer <TOKEN>",
          "Content-Type": "application/json"
      }
      
      response = requests.post(url, json=payload, headers=headers)
      print(response.status_code)
      print(response.text)
    • Key insights:
      • url: The endpoint defined in your Postman request.
      • payload: Dictionary-formatted body of the request if applicable (e.g., POST or PUT).
      • headers: Any authentication tokens, content types, or custom headers you’ve used.
      • response: Captures the result of the API call for logging, validation, or further processing.
    • When building multi-step workflows or adding dynamic values later, this exported Python code serves as the base structure to integrate templating (Jinja2) and external variable injection.

Jinja2: Dynamic Payload Templating

Jinja2 templating allows you to craft dynamic and reusable API payloads in your automation scripts. By separating the payload structure from the data, you can scale and maintain your API operations more efficiently. This section shows you how to create Jinja2 templates, use advanced features like loops and conditionals, and manage variables using YAML or JSON files.

  • Creating and Structuring Jinja2 Templates:
    • Step 1 – Create a Template File: Store your payload structure in a .j2 file:
      // templates/payload_template.j2
      {
        "device_name": "{{ device_name }}",
        "ip_address": "{{ ip_address }}",
        "role": "{{ role }}",
        "interfaces": [
          {% for intf in interfaces %}
          {
            "name": "{{ intf.name }}",
            "status": "{{ intf.status }}"
          }{% if not loop.last %},{% endif %}
          {% endfor %}
        ]
      }
      
      Template files should be stored in a dedicated templates/ directory.
    • Step 2 – Add placeholders using {{ }}: These are variable expressions that will be replaced at runtime using external values.
  • Supported Features: Variables, Loops, Conditionals:
    • Variables: Placeholders like {{ hostname }} allow you to replace content with actual data dynamically.
    • Loops: Use {% for item in list %} to iterate over arrays:
      
      {% for cmd in commands %}
      "{{ cmd }}"{% if not loop.last %},{% endif %}
      {% endfor %}
      
    • Conditionals: Create logic branches in your payload:
      
      {% if device_type == 'router' %}
      "feature": "routing"
      {% else %}
      "feature": "switching"
      {% endif %}
      
  • Storing Variables in Files (YAML/JSON):
    • Step 1 – Create a YAML or JSON file to store payload data:
      // payload_vars.yml
      device_name: core-switch-01
      ip_address: 192.168.1.10
      role: access
      interfaces:
        - name: Gig0/1
          status: up
        - name: Gig0/2
          status: down
      
    • Step 2 – Load and render in Python:
      from jinja2 import Environment, FileSystemLoader
      import yaml
      
      # Load variables
      with open('payload_vars.yml') as f:
          data = yaml.safe_load(f)
      
      # Set up Jinja2
      env = Environment(loader=FileSystemLoader('templates'))
      template = env.get_template('payload_template.j2')
      
      # Render the payload
      payload = template.render(data)
      print(payload)
    • This payload can now be used in your API call using requests.post().

Integrating Python Requests and Jinja2

Once you’ve created your Jinja2 template and stored your variables in a file, you can integrate everything with Python requests to dynamically build and send API requests. This section walks you through loading the data, rendering the template, validating the payload, and making the API call.

  • Loading Variables into Python:
    • Step 1 – Create a YAML file that stores the required variables:
      # payload_vars.yml
      device_name: edge-fw-01
      ip_address: 10.1.1.1
      role: firewall
      zones:
        - name: inside
          cidr: "192.168.1.0/24"
        - name: outside
          cidr: "0.0.0.0/0"
    • Step 2 – Load the file in your Python script:
      import yaml
      
      with open('payload_vars.yml') as f:
          variables = yaml.safe_load(f)
  • Rendering the Jinja2 Template:
    • Step 3 – Set up a Jinja2 environment and load the template:
      from jinja2 import Environment, FileSystemLoader
      
      env = Environment(loader=FileSystemLoader('templates'))
      template = env.get_template('api_payload.j2')
      payload = template.render(variables)
    • This produces a payload string with your values substituted. It can now be used as your API request body.
  • Validating Payloads Before Sending:
    • Step 4 – Convert the rendered template to JSON (if needed):
      import json
      
      try:
          json_payload = json.loads(payload)  # Ensures it's valid JSON
      except json.JSONDecodeError as e:
          print("Payload is not valid JSON:", e)
          exit(1)
    • Step 5 – Send the request using requests:
      import requests
      
      url = "https://api.example.com/firewalls"
      headers = {
          "Authorization": "Bearer <YOUR_TOKEN>",
          "Content-Type": "application/json"
      }
      
      response = requests.post(url, json=json_payload, headers=headers)
      print("Status Code:", response.status_code)
      print("Response Body:", response.text)
    • You now have an automated, template-driven API call that can adapt to different environments or inputs by simply changing your variable file.

Automating API Calls

Once you've verified individual API calls and implemented Jinja2 templating, the final step is to automate the process. This section demonstrates how to send API calls programmatically in Python, loop over multiple payloads for batch operations, and apply best practices for error handling.

  • Programmatically Sending Requests with Python:
    • Step 1 – Define the API request code as a reusable function:
      import requests
      
      def send_api_request(url, payload, headers):
          try:
              response = requests.post(url, json=payload, headers=headers)
              print(f"Response [{response.status_code}]: {response.text}")
              return response
          except requests.exceptions.RequestException as e:
              print(f"Request failed: {e}")
    • Step 2 – Prepare the inputs (URL, payload, headers): These can be defined inline or loaded from an external config file for flexibility.
  • Handling Batch Operations with Loops:
    • Step 3 – Create a list of variable files or datasets:
      import os
      import yaml
      from jinja2 import Environment, FileSystemLoader
      import json
      
      variable_files = ["vars/site1.yml", "vars/site2.yml", "vars/site3.yml"]
      env = Environment(loader=FileSystemLoader('templates'))
      template = env.get_template('api_payload.j2')
    • Step 4 – Loop through each file, render payload, and send call:
      url = "https://api.example.com/devices"
      headers = {
          "Authorization": "Bearer <YOUR_TOKEN>",
          "Content-Type": "application/json"
      }
      
      for file in variable_files:
          with open(file) as f:
              vars = yaml.safe_load(f)
          rendered = template.render(vars)
      
          try:
              payload = json.loads(rendered)
          except json.JSONDecodeError as e:
              print(f"Skipping {file} due to invalid JSON: {e}")
              continue
      
          send_api_request(url, payload, headers)
    • This allows you to apply a standard template across multiple devices, users, or configurations with different inputs.
  • Best Practices in Error Handling:
    • Use try/except blocks: Always wrap HTTP calls and template rendering in error-handling logic to catch runtime errors.
    • Log responses: Include logging of status codes, payloads, and error messages for troubleshooting and auditing.
    • Validate before sending: Ensure your JSON payload is valid using json.loads() before sending it.
    • Fail gracefully: Continue processing the next item if one fails—in critical automations, include retry logic or alerts.
    • Avoid hardcoding: Use config files or environment variables for things like API tokens, URLs, and other sensitive data.

Example: End-to-End Workflow

This section walks you through a full enterprise-grade example of automating an API workflow—from designing and testing in Postman to executing with Python requests, rendering Jinja2 templates, and integrating with Nornir to orchestrate actions across multiple devices. In this version, a single variables file is used per runbook, so you can apply the same workflow to multiple devices dynamically based on inventory metadata (hostname, IP, role, etc.).

  • Sample Directory/Project Structure:
    api-automation/
    ├── templates/
    │   └── device_template.j2
    ├── variables/
    │   └── deploy_variables.yml
    ├── scripts/
    │   ├── post_api_executor.py
    │   └── deploy_with_nornir.py
    ├── inventory/
    │   ├── hosts.yaml
    │   └── groups.yaml
    ├── config.yaml
    ├── run.py
    ├── requirements.txt
    ├── README.md
    

    This structure allows clear separation of concerns: templates for payloads, shared inputs for variables, and inventory for device-specific data.

  • Step-by-Step Walkthrough:
    • Step 1 – Design and Validate the API Request in Postman:
      • Create a collection and add your API request (e.g., POST /devices).
      • Use environment variables for tokens and payload content for device onboarding.
      • Test the payload and headers, then export the request as Python code using the </> button.
    • Step 2 – Create a Jinja2 Payload Template:
      // templates/device_template.j2
      {
        "hostname": "{{ inventory_hostname }}",
        "ip": "{{ host_ip }}",
        "role": "{{ role }}",
        "site_code": "{{ site_code }}",
        "interfaces": [
          {% for intf in interfaces %}
          {
            "name": "{{ intf.name }}",
            "description": "{{ intf.description }}"
          }{% if not loop.last %},{% endif %}
          {% endfor %}
        ]
      }

      This template uses dynamic values from the host inventory along with shared variables passed into the script.

    • Step 3 – Define Shared Runbook Variables:
      # variables/deploy_variables.yml
      site_code: "DAL01"
      interfaces:
        - name: "Gig0/1"
          description: "Uplink to core"
        - name: "Gig0/2"
          description: "Downlink to access"

      This allows one runbook to apply across the entire inventory using shared parameters + Nornir host facts.

    • Step 4 – Build the Nornir Task with Python Requests and Jinja2:
      from nornir import InitNornir
      from nornir.core.task import Task, Result
      from jinja2 import Environment, FileSystemLoader
      import requests, yaml, json
      
      # Load shared workflow variables once
      with open("variables/deploy_variables.yml") as f:
          runbook_vars = yaml.safe_load(f)
      
      # Set up Jinja2
      env = Environment(loader=FileSystemLoader("templates"))
      template = env.get_template("device_template.j2")
      
      def deploy_api_payload(task: Task) -> Result:
          # Merge task.host metadata into the vars
          rendered_payload = template.render(
              inventory_hostname=task.host.name,
              host_ip=task.host["ip"],
              role=task.host.get("role", "unknown"),
              **runbook_vars  # Inject shared vars from file
          )
      
          try:
              payload = json.loads(rendered_payload)
          except json.JSONDecodeError as e:
              return Result(host=task.host, failed=True, result=f"Payload JSON error: {e}")
      
          headers = {
              "Authorization": "Bearer <YOUR_TOKEN>",
              "Content-Type": "application/json"
          }
      
          try:
              response = requests.post("https://api.example.com/devices", json=payload, headers=headers)
              return Result(host=task.host, result=f"Status: {response.status_code}, Body: {response.text}")
          except requests.exceptions.RequestException as e:
              return Result(host=task.host, failed=True, result=f"API call failed: {e}")
      
      # Execute workflow
      nr = InitNornir(config_file="config.yaml")
      results = nr.run(task=deploy_api_payload)
    • Step 5 – Run the Workflow:

      From the CLI or your orchestrator, simply execute:

      python scripts/deploy_with_nornir.py

      This iterates through your inventory, dynamically renders the payload per host using shared variables, and sends the API request per device.

Conclusion

Throughout this blog post, we've walked step-by-step through building a modern, flexible, and scalable API workflow using PostmanPython's requests library, and Jinja2 templating. Whether you're managing network infrastructure, deploying cloud services, or automating device configuration, this architecture provides a robust, reusable foundation for your automation workflows.

Takeaways:

  • Postman is more than a testing tool—it's a visual API design platform that helps validate requests, handle authentication, and organize workflows into collections and environments.
  • You can export fully tested API calls directly from Postman into Python, instantly generating production-ready requests snippets as a starting point for automation.
  • Jinja2 templates make your API requests dynamic and reusable. By externalizing your payload structure and separating it from your variable data (in YAML/JSON), you scale your automation with minimal code changes.
  • Using Python and Jinja2 together, you can render complex payloads on the fly for any type of request, driven by data provided at runtime.
  • For real-world, enterprise-grade workflows, integrating with Nornir allows you to scale across multiple devices using inventory-driven automation, merging shared variables with host-specific context.
  • Along the way, we covered best practices like templating correctly, validating JSON payloads before sending, structuring your project for maintainability, and implementing clear error handling.

This workflow gives you the power to automate with confidence—across environments, at scale, and with clarity.

🚀 Whether you're automating firewall provisioning, device onboarding, or full-stack network deployments, these tools empower you to build structured, idempotent automation workflows that are easy to update, share, and grow.

Thanks for reading—happy automating! 🙌
If you found this helpful, consider sharing it with your peers or bookmarking it as a reference for your next API-driven project. The future of infrastructure is programmable—let's build it together. 💻🛠️