Skip to main content

Query historical data

Infrahub’s immutable history allows you to query the state of your infrastructure at any point in time. This guide shows you how to use temporal queries to access historical data, analyze changes, and understand past configurations.

Prerequisites

Before querying historical data, ensure you have:
  • Access to an Infrahub instance with historical data
  • GraphQL API access or SDK installed
  • Understanding of timestamps and time formats

Understand temporal queries

Infrahub tracks every change with an immutable timestamp. Temporal queries allow you to:
  • View infrastructure state at a specific point in time
  • Compare configurations before and after changes
  • Analyze historical trends and patterns
  • Investigate security incidents or unexpected changes
  • Meet compliance requirements with complete audit trails
See the Branch model at backend/infrahub/core/branch/models.py:219 for the get_branches_and_times_to_query implementation.

Query data at a specific time

Retrieve the state of objects as they existed at a specific timestamp.

Using GraphQL with the at parameter

query {
  InfraDevice(at: "2026-02-15T10:30:00Z") {
    edges {
      node {
        id
        name {
          value
        }
        hostname {
          value
        }
        status {
          value
        }
        site {
          node {
            display_label
          }
        }
      }
    }
  }
}
Parameters:
  • at: ISO 8601 timestamp (e.g., "2026-02-15T10:30:00Z")
This query returns all devices as they existed on February 15, 2026 at 10:30 AM UTC.

Using the Python SDK

from infrahub_sdk import InfrahubClient
from datetime import datetime, timezone

client = InfrahubClient()

# Query at a specific time
timestamp = datetime(2026, 2, 15, 10, 30, 0, tzinfo=timezone.utc)
devices = await client.all(
    kind="InfraDevice",
    at=timestamp.isoformat()
)

for device in devices:
    print(f"{device.name.value}: {device.hostname.value}")

Time format

Timestamps must be in ISO 8601 format: Valid formats:
  • 2026-02-15T10:30:00Z (UTC)
  • 2026-02-15T10:30:00+00:00 (UTC with offset)
  • 2026-02-15T05:30:00-05:00 (EST with offset)
  • 2026-02-15T10:30:00.123456Z (with microseconds)
Invalid formats:
  • 2026-02-15 (date only, missing time)
  • 10:30:00 (time only, missing date)
  • 02/15/2026 10:30 (US date format)

Query a single object’s history

Retrieve a specific object as it existed at different points in time.

Current state

query {
  InfraDevice(id: "<device-id>") {
    id
    name {
      value
    }
    hostname {
      value
    }
    status {
      value
    }
  }
}

Historical state

query {
  InfraDevice(id: "<device-id>", at: "2026-01-01T00:00:00Z") {
    id
    name {
      value
    }
    hostname {
      value
    }
    status {
      value
    }
  }
}
This shows the device as it was on January 1, 2026.

Compare data across time

Query the same data at different timestamps to see how it changed.

Using multiple queries

query {
  before: InfraDevice(id: "<device-id>", at: "2026-01-01T00:00:00Z") {
    hostname {
      value
    }
    status {
      value
    }
  }
  after: InfraDevice(id: "<device-id>", at: "2026-02-01T00:00:00Z") {
    hostname {
      value
    }
    status {
      value
    }
  }
}

Using the Python SDK

from datetime import datetime, timezone

# Query before and after
before_time = datetime(2026, 1, 1, tzinfo=timezone.utc)
after_time = datetime(2026, 2, 1, tzinfo=timezone.utc)

device_before = await client.get(
    kind="InfraDevice",
    id="<device-id>",
    at=before_time.isoformat()
)

device_after = await client.get(
    kind="InfraDevice",
    id="<device-id>",
    at=after_time.isoformat()
)

# Compare values
if device_before.hostname.value != device_after.hostname.value:
    print(f"Hostname changed: {device_before.hostname.value} -> {device_after.hostname.value}")

Query branch history

Each branch maintains its own timeline. Query historical data within a specific branch.

Query a branch at a specific time

query {
  InfraDevice(branch: "feature-network-redesign", at: "2026-02-15T10:30:00Z") {
    edges {
      node {
        id
        name {
          value
        }
        hostname {
          value
        }
      }
    }
  }
}
Parameters:
  • branch: Name of the branch to query
  • at: Timestamp to query
This returns devices as they existed in the feature-network-redesign branch at the specified time.

Using the Python SDK with branches

devices = await client.all(
    kind="InfraDevice",
    branch="feature-network-redesign",
    at="2026-02-15T10:30:00Z"
)

for device in devices:
    print(f"{device.name.value}")

Query attribute history

Retrieve the history of changes to specific attributes.

Query attribute values over time

query {
  InfraDevice(id: "<device-id>") {
    hostname {
      value
      updated_at
      is_from_profile
      is_protected
      is_visible
      owner {
        id
        display_label
      }
    }
  }
}
This returns the current hostname value along with metadata about when it was last updated and who owns it.

Access historical attribute values

query {
  jan: InfraDevice(id: "<device-id>", at: "2026-01-01T00:00:00Z") {
    hostname {
      value
      updated_at
    }
  }
  feb: InfraDevice(id: "<device-id>", at: "2026-02-01T00:00:00Z") {
    hostname {
      value
      updated_at
    }
  }
  mar: InfraDevice(id: "<device-id>", at: "2026-03-01T00:00:00Z") {
    hostname {
      value
      updated_at
    }
  }
}
This shows the hostname value at the beginning of three different months.

Query relationship history

Retrieve relationships between objects as they existed at specific times.

Current relationships

query {
  InfraDevice(id: "<device-id>") {
    interfaces {
      edges {
        node {
          id
          name {
            value
          }
          connected_endpoint {
            node {
              ... on InfraInterface {
                display_label
                device {
                  node {
                    display_label
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Historical relationships

query {
  InfraDevice(id: "<device-id>", at: "2026-01-01T00:00:00Z") {
    interfaces {
      edges {
        node {
          name {
            value
          }
          connected_endpoint {
            node {
              ... on InfraInterface {
                display_label
              }
            }
          }
        }
      }
    }
  }
}
This shows which interfaces were connected on January 1, 2026.

Common use cases

Audit who changed what and when

Track changes to critical infrastructure:
query {
  current: InfraDevice(id: "<device-id>") {
    hostname {
      value
      updated_at
      owner {
        display_label
      }
    }
  }
}
Combine with historical queries to build a complete audit trail.

Investigate security incidents

Determine the state of infrastructure before and after an incident:
from datetime import datetime, timezone

# Query state before incident
incident_time = datetime(2026, 2, 15, 14, 30, tzinfo=timezone.utc)
before_time = incident_time.replace(hour=incident_time.hour - 1)

devices_before = await client.all(
    kind="InfraDevice",
    at=before_time.isoformat()
)

devices_after = await client.all(
    kind="InfraDevice",
    at=incident_time.isoformat()
)

# Compare to find changes
for before, after in zip(devices_before, devices_after):
    if before.status.value != after.status.value:
        print(f"{before.name.value} status changed: {before.status.value} -> {after.status.value}")

Compliance reporting

Generate reports showing infrastructure state at specific audit dates:
query {
  InfraDevice(at: "2026-01-31T23:59:59Z") {
    edges {
      node {
        name {
          value
        }
        hostname {
          value
        }
        status {
          value
        }
        site {
          node {
            display_label
          }
        }
      }
    }
  }
}
This produces a point-in-time snapshot for January 31, 2026 audit requirements.

Analyze configuration drift

Compare configurations across time to identify drift:
from datetime import datetime, timedelta, timezone

async def analyze_drift(device_id: str, days: int = 30):
    now = datetime.now(timezone.utc)
    past = now - timedelta(days=days)
    
    device_now = await client.get(
        kind="InfraDevice",
        id=device_id,
        at=now.isoformat()
    )
    
    device_past = await client.get(
        kind="InfraDevice",
        id=device_id,
        at=past.isoformat()
    )
    
    changes = []
    if device_now.hostname.value != device_past.hostname.value:
        changes.append(f"Hostname: {device_past.hostname.value} -> {device_now.hostname.value}")
    
    if device_now.status.value != device_past.status.value:
        changes.append(f"Status: {device_past.status.value} -> {device_now.status.value}")
    
    return changes

# Check drift over last 30 days
drift = await analyze_drift("<device-id>", days=30)
for change in drift:
    print(change)

Limitations and considerations

Query performance

Historical queries may be slower than current-state queries because:
  • The database must reconstruct object state at the specified time
  • Branch isolation requires querying multiple timelines
  • Complex relationships increase computation time
Optimize performance by:
  • Querying specific objects instead of all objects
  • Limiting the number of relationships retrieved
  • Using filters to reduce result sets

Branch-aware vs. branch-agnostic data

Branch-agnostic data exists globally across all branches and times:
query {
  # Branch-agnostic data (same across all branches)
  CoreAccount(at: "2026-01-01T00:00:00Z") {
    edges {
      node {
        name {
          value
        }
      }
    }
  }
}
Branch-aware data varies by branch:
query {
  # Branch-aware data (different per branch)
  InfraDevice(branch: "main", at: "2026-01-01T00:00:00Z") {
    edges {
      node {
        name {
          value
        }
      }
    }
  }
}
See Branching topic for more on branch-aware vs. branch-agnostic data.

Verify historical data

Confirm that historical queries return expected results:
query {
  InfraDevice(id: "<device-id>", at: "2026-02-15T10:30:00Z") {
    id
    name {
      value
      updated_at
    }
    hostname {
      value
      updated_at
    }
  }
}
Check that:
  • updated_at timestamps are at or before the query timestamp
  • Values match expected historical state
  • Objects that didn’t exist at the query time return null

Build docs developers (and LLMs) love