API Request Inspection

The HFortix SDK provides powerful request inspection capabilities to help you debug, audit, and understand API interactions with FortiOS devices.

Overview

The request inspection feature allows you to:

  • Debug API calls: See exactly what was sent and received

  • Audit operations: Track all configuration changes

  • Troubleshoot errors: Understand why requests fail

  • Learn the API: See how high-level operations translate to HTTP requests

  • Build custom tools: Access raw request/response data

Quick Start

Basic Usage

from hfortix import FortiOS

fgt = FortiOS("192.168.1.99", token="your_token")

# Make an API call
response = fgt.api.cmdb.firewall.address.get("server1")

# Inspect the last request
last_request = fgt.http_api_request()

print(f"Method: {last_request['method']}")
print(f"URL: {last_request['url']}")
print(f"Status: {last_request['status_code']}")
print(f"Response: {last_request['response']}")

FortiManager Inspection

For FortiManager, use fmg_api_request():

from hfortix import FortiManager

fmg = FortiManager("192.168.1.100", username="admin", password="password")

# Make an API call
response = fmg.dvmdb.adom.get()

# Inspect the last request
last_request = fmg.fmg_api_request()

print(f"JSON-RPC ID: {last_request['id']}")
print(f"Method: {last_request['method']}")
print(f"URL: {last_request['url']}")
print(f"Response: {last_request['response']}")

Request Object Structure

FortiOS Request Object

The http_api_request() method returns a dictionary with:

{
    'method': str,           # HTTP method (GET, POST, PUT, DELETE)
    'url': str,              # Full request URL
    'headers': dict,         # Request headers
    'body': dict,            # Request body (for POST/PUT)
    'status_code': int,      # HTTP status code
    'response': dict,        # Parsed response body
    'duration': float,       # Request duration in seconds
    'timestamp': str         # ISO timestamp of request
}

FortiManager Request Object

The fmg_api_request() method returns a dictionary with:

{
    'id': int,               # JSON-RPC request ID
    'method': str,           # JSON-RPC method (get, set, add, etc.)
    'url': str,              # API endpoint URL
    'params': list,          # JSON-RPC params array
    'response': dict,        # Full JSON-RPC response
    'result': dict,          # Response result object
    'status': dict,          # Response status (code, message)
    'duration': float,       # Request duration in seconds
    'timestamp': str         # ISO timestamp of request
}

Common Use Cases

1. Debugging Failed Requests

from hfortix import FortiOS, APIError

fgt = FortiOS("192.168.1.99", token="your_token")

try:
    # This might fail
    fgt.api.cmdb.firewall.policy.post({
        "policyid": 100,
        "name": "test-policy"
        # Missing required fields
    })
except APIError as e:
    # Inspect what went wrong
    last_req = fgt.http_api_request()

    print(f"Request failed:")
    print(f"  Method: {last_req['method']}")
    print(f"  URL: {last_req['url']}")
    print(f"  Body: {last_req['body']}")
    print(f"  Status: {last_req['status_code']}")
    print(f"  Error: {last_req['response']}")

2. Audit Trail

import json
from datetime import datetime

# Store all API operations for audit
audit_log = []

fgt = FortiOS("192.168.1.99", token="your_token")

# Create address
fgt.api.cmdb.firewall.address.post({
    "name": "server1",
    "subnet": "10.0.1.10 255.255.255.255"
})
audit_log.append(fgt.http_api_request())

# Update address
fgt.api.cmdb.firewall.address.put("server1", {
    "comment": "Production server"
})
audit_log.append(fgt.http_api_request())

# Delete address
fgt.api.cmdb.firewall.address.delete("server1")
audit_log.append(fgt.http_api_request())

# Save audit log
with open("audit.json", "w") as f:
    json.dump(audit_log, f, indent=2)

3. Performance Analysis

import statistics

fgt = FortiOS("192.168.1.99", token="your_token")

durations = []

# Test multiple requests
for i in range(10):
    fgt.api.cmdb.firewall.address.get()
    req = fgt.http_api_request()
    durations.append(req['duration'])

print(f"Average latency: {statistics.mean(durations):.3f}s")
print(f"Min latency: {min(durations):.3f}s")
print(f"Max latency: {max(durations):.3f}s")

4. Learning the API

# Make a high-level call
fgt = FortiOS("192.168.1.99", token="your_token")

response = fgt.api.cmdb.firewall.policy.post({
    "policyid": 100,
    "name": "allow-web",
    "srcintf": [{"name": "internal"}],
    "dstintf": [{"name": "wan1"}],
    "srcaddr": [{"name": "all"}],
    "dstaddr": [{"name": "all"}],
    "action": "accept",
    "schedule": "always",
    "service": [{"name": "HTTP"}]
})

# See exactly what was sent
req = fgt.http_api_request()

print("To create this policy, the SDK sent:")
print(f"  {req['method']} {req['url']}")
print(f"  Body: {json.dumps(req['body'], indent=2)}")

Integration Examples

Logging Integration

import logging
import json

logging.basicConfig(
    level=logging.DEBUG,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger('fortios_api')

fgt = FortiOS("192.168.1.99", token="your_token")

# Make API call
fgt.api.cmdb.firewall.address.get("server1")

# Log the request
req = fgt.http_api_request()
logger.info(
    f"API Request: {req['method']} {req['url']} - "
    f"Status: {req['status_code']} - "
    f"Duration: {req['duration']:.3f}s"
)

# Log full details at debug level
logger.debug(f"Full request: {json.dumps(req, indent=2)}")

Transaction Inspection

When using transactions, you can inspect individual operations:

from hfortix import FortiOS

fgt = FortiOS("192.168.1.99", token="your_token")

with fgt.transaction() as txn:
    # First operation
    fgt.api.cmdb.firewall.address.post({"name": "addr1", "subnet": "10.0.1.1/32"})
    req1 = fgt.http_api_request()
    print(f"Op 1: {req1['method']} {req1['url']}")

    # Second operation
    fgt.api.cmdb.firewall.address.post({"name": "addr2", "subnet": "10.0.1.2/32"})
    req2 = fgt.http_api_request()
    print(f"Op 2: {req2['method']} {req2['url']}")

    # Show transaction details (FortiOS 7.4.1+)
    details = txn.show()
    print(f"Transaction commands: {details}")

# After commit, you can still inspect the last operation
final_req = fgt.http_api_request()
print(f"Final operation: {final_req['method']} {final_req['url']}")

Best Practices

1. Always Check After Critical Operations

# Create important object
fgt.api.cmdb.firewall.policy.post({...})

# Verify it succeeded
req = fgt.http_api_request()
if req['status_code'] != 200:
    logger.error(f"Policy creation failed: {req['response']}")
    raise Exception("Failed to create policy")

2. Use for Debugging, Not Production Logic

# ❌ Bad: Don't rely on http_api_request for application logic
response = fgt.api.cmdb.firewall.address.get("server1")
req = fgt.http_api_request()
if req['status_code'] == 200:
    # Use response directly, not req['response']

# ✅ Good: Use for debugging and monitoring
try:
    response = fgt.api.cmdb.firewall.address.get("server1")
    # Use response here
except APIError as e:
    # Use http_api_request to understand the error
    req = fgt.http_api_request()
    logger.debug(f"Request details: {req}")

3. Sanitize Before Logging

def sanitize_request(request):
    """Remove sensitive data from request"""
    sanitized = request.copy()

    # Remove authentication headers
    if 'headers' in sanitized:
        headers = sanitized['headers'].copy()
        headers.pop('Authorization', None)
        headers.pop('X-CSRFTOKEN', None)
        sanitized['headers'] = headers

    # Remove sensitive fields from body
    if 'body' in sanitized and isinstance(sanitized['body'], dict):
        body = sanitized['body'].copy()
        body.pop('password', None)
        body.pop('secret', None)
        sanitized['body'] = body

    return sanitized

# Always remove sensitive data before logging/storing
req = fgt.http_api_request()
safe_req = sanitize_request(req)
logger.info(f"Request: {safe_req}")

Limitations

Only Last Request

The SDK stores only the most recent request. For multiple requests, implement your own history:

# This only shows the last request
fgt.api.cmdb.firewall.address.get("addr1")
fgt.api.cmdb.firewall.address.get("addr2")
req = fgt.http_api_request()  # Only shows addr2 request

FortiOS vs FortiManager

Use the correct method for each platform:

  • FortiOS: fgt.http_api_request()

  • FortiManager: fmg.fmg_api_request()

See Also

  • /fortios/guides/transactions - Batch transactions

  • /fortios/guides/error-handling - Error handling patterns

  • /fortios/guides/debugging - Advanced debugging techniques

  • /fortios/guides/audit-logging - Enterprise audit logging