Skip to main content

Query historical data

You can read infrastructure data as it existed at previous points in time, and compare how it changed between them. A timestamped query answers what current data cannot: which devices and interfaces existed for a site during last night's incident, which interface attributes changed between the last known-good time and now, or which prefixes were assigned to a site on a given date.

Infrahub offers three ways to ask about the past, and they answer different questions:

What you needHow to askWhere it is covered
The state of the data at one point in timeat and branch on a readQuery data at a specific time
Which objects and attributes changed between two pointsDiffUpdate, then DiffTreeCompare changes between two timestamps
Which operations were performed between two points, and by whomsince and until on an event queryQuerying the activity log over the API

The first two read the data itself. The third reads the record of operations that produced it — see Immutable history for which question each one answers.

Specify a branch and a time​

To read the state of the data at a specific point in time, specify a branch and a timestamp. Infrahub returns the objects, attribute values, and related objects that were valid at that time. If you do not set either one, Infrahub returns the current data from the default branch.

State to readBranchTime
Production as it standsdefaultnow
Production during last night's incidentdefaulta time within the incident
The change someone is proposingtheir branchnow
The default branch just before that change mergeddefaulta time before the merge

How historical queries work​

When you add a timestamp to a query, Infrahub evaluates each requested object using the attribute values and relationships that were valid at that time. You use the same query structure as you do for current data; the timestamp changes which versions Infrahub returns.

Infrahub can return those versions because a change records a new value alongside the previous one rather than replacing it. History is recorded per object, per attribute, and per relationship, which is what lets a comparison identify the specific fields or connections that changed and what they held before. See How Infrahub preserves history for the storage model.

The schema is evaluated for the same point in time. If the schema changed after the timestamp you request, Infrahub loads the schema as it was then, so the query sees the objects, attributes, and relationships that existed at that point rather than the current ones.

A timestamp applies to read operations only — queries, in GraphQL terms. If a query document contains a mutation, Infrahub ignores the timestamp and applies the mutation at the current time.

Once Infrahub records a version, a change made after it does not alter that version. See Immutable history for more detail on how Infrahub preserves those versions and how immutable history relates to branches and the Activity log.

Query data at a specific time​

Use a timestamp when you need the objects, attributes, and relationships that existed at a known point — for example, during an incident or before a change.

You can specify a time when viewing or querying Infrahub data through the web interface, GraphQL, REST API, or Python SDK. Use the web interface for interactive investigation and the APIs or SDK when you need a repeatable or programmatic query.

Select the time selector — the calendar and clock icon beside the branch selector — and choose a date and time. The picker uses your browser's time zone rather than UTC, and offers past dates and times only. Until you set a time, the selector displays only the icon; once you set one, the bar beside it displays Current view time with your selection. Select the × beside the displayed time to return to the current time.

The selected time stays applied as you navigate, and it covers more than object data. Attribute values, relationships, the schema, and the navigation menu are all loaded for that timestamp, so an object's fields and the navigation tree can differ from what the current schema defines. Diffs, Proposed Changes, tasks, and events have no time-aware query, so those screens show current data even while Current view time is displayed.

The selected time is part of the page URL as the at parameter, and it is preserved as you navigate, so you can bookmark a historical view or share it as a link.

warning

Infrahub does not prevent editing while a past time is applied, and it does not warn you. A change you save — including a deletion — applies to the current data at the current time, not to the time you are viewing. Treat a view with Current view time set as read-only.

The time selector, with a past time applied

Compare changes between two timestamps​

Comparing two timestamps takes two steps: Infrahub calculates the diff, then you retrieve it. A diff is stored rather than computed when you ask for it, which is why a query for a period that has never been calculated returns null instead of a result.

Send the DiffUpdate mutation to calculate the diff. A custom period requires both the time range and a name to store it under — without a name, Infrahub rejects the request. Pass wait_until_completion: true when the mutation should return only once the calculation has finished, rather than starting a background task.

Calculate a diff for a period
mutation CalculateIncidentDiff {
DiffUpdate(
data: {
branch: "main"
name: "incident-2026-03-09"
from_time: "2026-03-09T00:00:00Z"
to_time: "2026-03-10T00:00:00Z"
}
wait_until_completion: true
) {
ok
}
}

Then retrieve it with the same branch and time range you calculated it for, using DiffTree for the changed nodes or DiffTreeSummary when you only need counts. The timestamps do not need to align with a branch point or a Proposed Change, so you can compare any useful period, such as the hours around an incident.

Retrieve a custom period by that period rather than by its name. DiffTree looks for a diff covering the range it computes by default, from the branch point to the present, so it does not find one stored for a different range and returns null. DiffTreeSummary does not accept name at all and returns an error. A name is still worth setting, because DiffUpdate requires one for a custom period.

Retrieve the calculated diff
query IncidentChanges {
DiffTree(
branch: "main"
from_time: "2026-03-09T00:00:00Z"
to_time: "2026-03-10T00:00:00Z"
) {
num_added
num_updated
num_removed
nodes {
kind
label
status
attributes {
name
status
}
}
}
}

Behavior to expect:

  • Omit from_time and the comparison starts at the branch's branched_from timestamp.
  • Omit to_time and the comparison runs to the present.
  • Narrow the result with filters, which accepts ids, kind, namespace, and status. The kind, namespace, and status filters each take includes and excludes lists.
  • Page through results with limit and offset.
  • The base of the comparison is the default branch. Name a feature branch to compare it with the default branch across the requested period, or name the default branch to compare it with itself over time.
  • Pass proposed_change_id instead of a branch and period to retrieve the diff for a Proposed Change.
  • A null result means that no diff covering the requested period has been calculated, not that nothing changed.

In the web interface, a branch's Branch view shows its diff and lets you refresh it, whether or not a Proposed Change exists for that branch, and a Proposed Change shows the diff between its source and target branches. When you need review, validation, and checks alongside the comparison, use a Proposed Change.

Choose an absolute timestamp or relative offset​

Use a relative offset when you are investigating from the current time. Use an absolute timestamp when the query needs to resolve to the same time each time it runs — for example, for an audit answer, post-incident report, or reproducible analysis.

FormExampleResolves to
ISO 8601 with a zone or offset2026-03-09T14:00:00Z or 2026-03-09T15:00:00+01:00The specified instant
ISO 8601 without a zone2026-03-09T14:00:00The same wall-clock time, interpreted as UTC
Date only2026-03-0912:00 UTC on that day
Offset from now30s, 45m, 6h, 2h30mThat interval before the current time

A date without a time resolves to midday rather than midnight, so include an explicit time when a day boundary matters. Relative offsets support seconds, minutes, and hours, including combined values such as 2h30m. They do not support day or week units; use an absolute timestamp for those intervals.

Understand branch history limits​

Every branch reads history through the default branch, so the earliest time you can query is the default branch's creation time — the point at which Infrahub was first initialized. That floor is the same on a feature branch as on the default branch, because creating a branch records where it diverged rather than copying the dataset. A branch created this morning can still be read back to the default branch's creation, with the changes recorded on the branch since it diverged added to that history.

If you request an earlier time than that, Infrahub rejects the query rather than returning partial data:

Requested time '2026-01-05T00:00:00Z' is before branch 'main' was created at '2026-02-01T09:14:22.481000Z'.

Infrahub validates the earliest time only. If you request a time later than the current time, Infrahub accepts it and returns the current data, because every attribute value and relationship that is valid now is also valid at a later time. In the web interface, the time selector offers past dates and times; through GraphQL, the REST API, and the Python SDK, you can request a future time. If you build the timestamp from a variable or a calculation, confirm it resolves to a past time — nothing in the response indicates that a future timestamp was used.

  • Immutable history — how Infrahub preserves previous values and relationships
  • Branches — how branches diverge, share history, and merge
  • Activity log — which operations occurred, when, and by whom
  • Proposed Changes — compare a branch with its base with review, validation, and checks