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#