Response Objects#
All HFortix API methods return FortiObject or FortiObjectList instances that provide
clean attribute access to response data, plus metadata about the API request.
Quick Reference#
result = fgt.api.cmdb.firewall.address.post(
name='webserver',
subnet='192.168.1.100 255.255.255.255'
)
# Access response data as attributes
print(result.name) # 'webserver'
print(result.subnet) # '192.168.1.100 255.255.255.255'
# Check if configuration actually changed
if result.fgt_revision_changed:
print(f"Config changed! New revision: {result.fgt_revision}")
else:
print("No configuration change (object already existed with same values)")
# HTTP/API metadata
print(result.http_status_code) # 200
print(result.http_status) # 'success'
print(result.http_response_time) # 45.2 (milliseconds)
Response Properties#
HTTP/API Status Properties#
Property |
Type |
Description |
|---|---|---|
|
|
HTTP status code (200, 400, 404, 500, etc.) |
|
|
API status: |
|
|
HTTP method used: |
|
|
Response time in milliseconds |
|
|
Summary dict with all HTTP stats |
FortiGate Metadata Properties#
Property |
Type |
Description |
|---|---|---|
|
|
Current configuration revision number |
|
|
True if configuration was modified |
|
|
Previous revision (before this change) |
|
|
Virtual domain name |
|
|
Primary key of created/modified object |
|
|
Device serial number |
|
|
FortiOS version (e.g., |
|
|
Firmware build number |
|
|
API path segment (e.g., |
|
|
API endpoint name (e.g., |
Pagination Properties (for list queries)#
Property |
Type |
Description |
|---|---|---|
|
|
Number of objects returned |
|
|
Total objects matching query |
|
|
True if pagination limit was hit |
|
|
Index for next page |
FortiManager Proxy Properties#
When using FortiManager proxy, additional properties are available:
Property |
Type |
Description |
|---|---|---|
|
|
FortiManager status code |
|
|
FortiManager status message |
|
|
Proxy request status |
|
|
Target device name |
|
|
FortiManager request ID |
|
|
Raw FortiManager response |
Data Conversion Properties#
Property/Method |
Type |
Description |
|---|---|---|
|
|
Object data as dictionary |
|
|
Object data as pretty-printed JSON string |
|
|
Full API response envelope (includes metadata) |
|
|
Alias for |
|
|
Get raw field value (not auto-flattened) |
Example:
address = fgt.api.cmdb.firewall.address.get(mkey='webserver')
# As dictionary - for programmatic access
data = address.dict
data['name'] # 'webserver'
# As JSON - for display/logging
print(address.json)
# {
# "name": "webserver",
# "subnet": "192.168.1.100 255.255.255.255",
# "comment": "Production server"
# }
# Raw envelope - for debugging/metadata
address.raw
# {'http_status': 200, 'status': 'success', 'vdom': 'root', 'results': {...}}
Understanding fgt_revision_changed#
The fgt_revision_changed property is crucial for understanding whether your API call actually modified the FortiGate configuration.
Why This Matters#
FortiOS is idempotent - if you create an object that already exists with identical values, it returns HTTP 200 (success) but doesn’t actually change the config. This property tells you the difference:
# First creation - config changes
result1 = fgt.api.cmdb.firewall.address.post(
name='webserver',
subnet='192.168.1.100 255.255.255.255'
)
print(result1.fgt_revision_changed) # True - new object created
print(result1.fgt_revision) # '12345'
# Identical call - NO config change
result2 = fgt.api.cmdb.firewall.address.post(
name='webserver',
subnet='192.168.1.100 255.255.255.255'
)
print(result2.fgt_revision_changed) # False - already exists, same values
print(result2.fgt_revision) # '12345' - same revision
# Update with different value - config changes
result3 = fgt.api.cmdb.firewall.address.put(
mkey='webserver',
comment='Updated comment'
)
print(result3.fgt_revision_changed) # True - config modified
print(result3.fgt_revision) # '12346' - new revision
print(result3.fgt_old_revision) # '12345' - previous revision
Use Cases#
1. Change Detection in Automation
def apply_config_if_changed(fgt, addresses):
"""Only report actual changes."""
changes = []
for addr in addresses:
result = fgt.api.cmdb.firewall.address.post(**addr)
if result.fgt_revision_changed:
changes.append(addr['name'])
if changes:
print(f"Modified: {', '.join(changes)}")
else:
print("No changes needed - config already up to date")
2. Audit Logging
def create_address_with_audit(fgt, **kwargs):
"""Log only actual configuration changes."""
result = fgt.api.cmdb.firewall.address.post(**kwargs)
if result.fgt_revision_changed:
log.info(f"CONFIG CHANGED: Created {kwargs['name']}, "
f"revision {result.fgt_old_revision} → {result.fgt_revision}")
else:
log.debug(f"No change: {kwargs['name']} already exists with same config")
return result
3. Rollback Tracking
# Track revisions for potential rollback
changes = []
result = fgt.api.cmdb.firewall.address.post(name='test', subnet='10.0.0.1/32')
if result.fgt_revision_changed:
changes.append({
'object': 'test',
'old_revision': result.fgt_old_revision,
'new_revision': result.fgt_revision
})
Data Access Patterns#
Attribute Access (Recommended)#
# Clean attribute access for known fields
address = fgt.api.cmdb.firewall.address.get(mkey='webserver')
print(address.name)
print(address.subnet)
print(address.comment)
Dict-Style Access#
# Dict-style for dynamic field names
field_name = 'subnet'
print(address[field_name])
# With default value
print(address.get('comment', 'No comment'))
Auto-Flattening of Member Tables#
HFortix automatically simplifies member table fields:
# FortiOS API returns complex structure:
# {"srcaddr": [{"name": "addr1", "q_origin_key": "addr1"}, {"name": "addr2", ...}]}
policy = fgt.api.cmdb.firewall.policy.get(mkey=1)
# HFortix auto-flattens to simple list:
print(policy.srcaddr) # ['addr1', 'addr2']
# Access full raw data when needed:
print(policy.get_full('srcaddr'))
# [{"name": "addr1", "q_origin_key": "addr1"}, {"name": "addr2", ...}]
FortiObjectList (List Responses)#
GET requests without mkey return FortiObjectList:
addresses = fgt.api.cmdb.firewall.address.get()
# Iterate
for addr in addresses:
print(addr.name)
# Length
print(len(addresses)) # 42
# Index access
first = addresses[0]
last = addresses[-1]
# Slice
first_ten = addresses[:10]
# List comprehension
names = [addr.name for addr in addresses]
# Filter
internal = [a for a in addresses if a.subnet.startswith('10.')]
# Check pagination
if addresses.fgt_limit_reached:
print(f"More results available, next index: {addresses.fgt_next_idx}")
Converting to Dict/JSON#
# Single object
address = fgt.api.cmdb.firewall.address.get(mkey='webserver')
# Using properties (recommended)
data = address.dict # Dictionary
json_str = address.json # Pretty-printed JSON string
envelope = address.raw # Full API envelope with metadata
# Using methods (equivalent)
data = address.to_dict() # Same as .dict
# List to dicts
addresses = fgt.api.cmdb.firewall.address.get()
all_data = [addr.dict for addr in addresses]
# Export to file
with open('addresses.json', 'w') as f:
import json
json.dump([addr.dict for addr in addresses], f, indent=2)
See Also#
Endpoint Methods - GET, POST, PUT, DELETE methods
Error Handling - Exception handling patterns
Audit Logging Guide - Tracking configuration changes