For the complete documentation index, see llms.txt. This page is also available as Markdown.
GuideBeta

Investigating changes with audit logs

Reconstruct who changed an entity, what they changed, and when with entityHistory and auditEvents queries.

Business Data Repository records creates, updates, and deletes on eleven entity types, listed below. You read that record through two queries. This scenario walks you through a compliance investigation: an asset's configuration changed unexpectedly, and you need to establish who changed it, what the old and new values were, and what else that actor touched in the same session.

By the end, you'll be able to read one entity's full change history, decode the stored before-and-after values, widen the search across a whole workspace, and tie an audit entry back to your own application logs.

Prerequisites

You need an access token for the workspace you work in. The API takes the workspace from the token, so no operation on this page asks for a workspace ID. See Authentication for how to get a token and how the API picks the workspace.

You also need an audited entity that has been changed at least once. The first step below shows how to find its ID, including when the entity was already deleted. See Which entities are audited for the list of audited entities.

How audit works

The API writes every recorded change as an audit event, and events never change after they're written. Business Data Repository keeps events with no time limit. This section covers what one event holds, which entities produce events, and how to read the recorded values.

What one event records

An audit event describes the actor, the affected entity, the type of change, the before and after values, and the details of the request, so you can match the change against your own logs.

Field
Used for

actor

Who made the change: a User, an Integration, or a SystemActor.

aggregateType / aggregateId

Which entity changed, as a type code plus its ID.

eventType

The type of change: CREATED, UPDATED, DELETED, or RESTORED.

eventData

The changed fields with their old and new values, plus the entity's stored state before and after the change.

occurredAt

When the change happened. It comes from the event ID, so ordering by time always matches ordering by ID.

traceId

The W3C trace ID of the request or other work that caused the change, for matching audit events with application logs.

ipAddress / userAgent

Where the request came from.

sourceType

The kind of source the change came from. Every event has API.

workspace

The workspace the change belongs to, never null. Both queries return only events from your token's workspace, including changes the platform made in it on its own. To spot those, check whether actor is a SystemActor.

Which entities are audited

Eleven entity types are recorded. aggregateType always contains one of these codes, and no other value:

Code
Recorded event types

asset

CREATED, UPDATED, DELETED

asset_group

CREATED, UPDATED, DELETED

catalog_item

CREATED, UPDATED, DELETED

custom_field_definition

CREATED, UPDATED, DELETED

device

CREATED, UPDATED, DELETED

geo_object

CREATED, UPDATED, DELETED

integration

CREATED, UPDATED, DELETED

inventory

CREATED, UPDATED, DELETED

member

CREATED, UPDATED, DELETED

schedule

CREATED, UPDATED, DELETED

workspace

CREATED, UPDATED, DELETED, RESTORED

catalog_item covers every catalog item, such as asset types, tags, and catalogs. Archiving a custom field definition and restoring it with the restore action are both recorded as UPDATED, because neither deletes the definition. RESTORED is recorded only for a workspace that Navixy Console reactivates.

Some changes aren't recorded in the audit log:

  • Sign-ins and other authentication events.

  • Adding assets to a group and removing them. The group's history field records membership instead.

  • Device relations and device identifiers.

  • Links between devices and inventories. The device's inventoryHistory field records them instead.

  • User accounts. Only the member record that joins a user to a workspace is audited.

What eventData contains

eventData holds the answer to the investigator's main question: what exactly changed. The other event fields say who changed which entity and when, while eventData holds the values themselves, so you can see the old and the new state without any other source. It's a JSON scalar with two keys:

changedFields holds one entry per field whose value differs, each with its old and new value. Every update sets a new updatedAt, so on an UPDATED event changedFields always lists at least that field.

additionalContext holds the wider context. operation names what happened (CREATE, UPDATE, DELETE, or RESTORE). before_state and after_state hold the entity's stored state immediately before and immediately after the change. Where changedFields shows only what changed, these two show the whole record.

The keys in changedFields and in both states are the property names that Business Data Repository stores internally, not the GraphQL field names. For example, an asset's type is stored as assetTypeId, and all its custom field values are in one key, customFieldsData. A reference to another entity is stored as that entity's ID. The API doesn't guarantee these keys, so use them to read a change rather than in automated logic.

Which of the two you get depends on the operation, because a newly created entity has no "before" and a deleted one has no "after":

Operation

before_state

after_state

changedFields

CREATE

absent

present

every field that has a value, with "old": null

UPDATE

present

present

only the fields that differ

DELETE

present

absent

every field that had a value, with "new": null

RESTORE

present

present

only the fields that differ

eventData is plain JSON rather than a typed GraphQL object, so you can't select individual keys inside it in the query. Request it as one field and read the values in your own code.

How to track a logged change

1

Find the ID of the entity to investigate

Both audit queries work with entity IDs, so start by finding the ID of the entity you're investigating. List queries take filters that narrow down the output by what you already know, such as a name or a hardware identifier. For example, to find a truck by a fragment of its name, run this query:

Response:

The same pattern works for the other audited entity types: narrow down devices by identifierContains (a fragment of an IMEI or serial number) or titleContains and use titleContains for geo objects, schedules, and asset groups.

If the entity was deleted, list queries no longer return it, but the audit trail still knows it. Search the deletions of its type and take aggregateId from the matching event:

The response lists one entry per deleted asset. aggregateId is the deleted entity's ID:

Either way, the ID you found is the entity ID for the rest of the scenario.

2

Read the entity's history

Start with entityHistory, which returns every audit event for one entity, newest first:

Response:

The default ordering is { field: OCCURRED_AT, direction: DESC }, so the most recent change is first. That's the one to look at.

actor is the Actor interface. Select id and title directly, or use an inline fragment such as ... on User { email } when you need type-specific fields.

3

Identify the changes

Add eventData to see what changed, field by field. Narrow down the response to the single event you're looking for with the aggregateIds and eventTypes filters:

Response:

changedFields shows the answer directly: Anna Weber renamed the truck. The updatedAt and version entries appear alongside it because every successful update of an asset sets a new updatedAt and increases the version that optimistic locking checks.

4

Widen the search to the entire workspace

You now know who made the change. To find out what else that actor did around the same time, switch to auditEvents, which covers the whole workspace rather than one entity, and narrow it down by actor and period:

Response:

Switching direction to ASC puts the events in the order they happened, which is what you want when retracing a sequence of actions.

Note that the first two events share a traceId. That means they came from the same request: renaming the asset and deleting a device happened in one operation.

5

Match audit events with your application logs

traceId is the W3C trace context trace ID from the request's traceparent header, when that header is present and well-formed. If the header is missing or malformed (wrong version, uppercase hex, an all-zero ID), the API generates a trace ID instead. Either way, you see the ID in the event's traceId and in extensions.traceId of any error the request raised. A generated ID has no match in your own logs, since your client never sent it.

A change that the platform makes on its own has a SystemActor as actor and the trace ID of what caused it, such as a request or a command from Navixy Console. A scheduled run, or a Console command sent without a trace ID, gets a generated trace ID.

One API request can change several entities, and every event it produces has the same traceId. Filtering by it returns that whole set:

When your client sends the traceparent header on the request, the same ID appears in your own logs and tracing tools, so it's the shared value that connects the audit trail to them. Use it in the other direction too: given a suspicious request in your logs, this query tells you exactly which entities it modified.

Filtering

AuditEventFilter applies to both queries. Values within one field are combined with OR, and separate fields are combined with AND. Empty arrays are ignored.

Field
Type
Use for

actorIds

[ID!]

Restricting to specific users or integrations

aggregateTypes

[Code!]

Restricting to entity types, such as ["asset", "device"]

aggregateIds

[ID!]

Restricting to specific entity IDs

eventTypes

[AuditEventType!]

Restricting to CREATED, UPDATED, DELETED, or RESTORED

sourceTypes

[SourceType!]

Every event has the source type API

traceId

String

Grouping every change made by one request

from / to

DateTime

Limiting the period

For example, to see every deletion of an asset or a device in one day, run this query:

Ordering and pagination

OCCURRED_AT is the only field you can order by, in either direction. Both queries return an AuditEventConnection and follow the standard cursor pagination described in Pagination:

Request total { count } only when you need to show a total, because counting every matching event is a second database query in addition to fetching the page. To move to the next page, check pageInfo.hasNextPage instead. It tells you whether another page exists and costs nothing extra.

You can also fetch a single audit event through the node query, since AuditEvent implements Node:

Error handling

Audit queries are read-only, so they return the standard errors documented in Error handling.

Error
Cause
How to resolve it

entityId isn't a valid ID, or first or last is out of range

Check the ID format, and keep first and last between 1 and 100

UNAUTHORIZED (401)

The token is malformed, expired, or issued by another identity service. A request with no token gets PERMISSION_DENIED.

Re-authenticate, see Authentication

How to handle an empty result set

An empty nodes array is a normal response, not an error. Work through the likely causes in this order:

  1. The filter matches no recorded value. Every event has the source type API, so a sourceTypes filter without API returns nothing. RESTORED is recorded only for a workspace.

  2. The change isn't audited. Only the eleven types listed above produce events, and some changes aren't recorded at all, such as a change to a user account or an asset group membership. See Which entities are audited.

  3. The period is wrong. from and to are compared against occurredAt in UTC. Make sure your values are in UTC too.

  4. The entity belongs to another workspace. entityHistory only searches the workspace that your token names, so an entity from a different workspace returns nothing.

See also

  • Audit: Complete reference for all audit operations and types

  • Error handling: Understand error structure, codes, and common error scenarios

  • Pagination: Page through large result sets with cursors

  • Optimistic locking: Prevent concurrent updates from overwriting each other with version

Last updated

Was this helpful?