dataverse_get_attribute_change_history
Retrieve the audit trail for a single column of a specific record to see who changed that field and when. Includes audit configuration diagnostics when no changes are found.
Instructions
Retrieve the audit trail for ONE COLUMN of ONE RECORD — who changed this field.
The column-scoped sibling of dataverse_retrieve_record_change_history, which returns every change to the record across all audited columns. Use this one when the question is about a single field ('when did this account's creditlimit last change, and to what?'); it answers from the server rather than making you filter a whole record's history client-side.
MIND THE SINGULAR/PLURAL SPLIT — this tool takes the SAME TABLE TWICE, under two different names, and they are NOT interchangeable:
entity_set_name — the PLURAL collection name ('accounts'). This is the only one sent to the function, inside the target EntityReference.
table_logical_name — the SINGULAR logical name ('account'). Never sent to the function; used only by the audit-configuration probes described below, which address table metadata by LogicalName. Both are required because the plural cannot be derived from the singular (or vice versa: 'webresource' -> 'webresourceset'), and it cannot be looked up either — $filter on the root EntityDefinitions collection is refused with HTTP 400 [0x80060888]. Use dataverse_get_entity_sets to confirm the plural.
ON A 404, READ THE ERROR CODE — DO NOT ASSUME WHICH FAILURE IT IS. Live-confirmed on this function, and the older warning that a missing record and a wrong entity set were indistinguishable was wrong:
a well-formed but NONEXISTENT record id with the CORRECT plural entity set returns HTTP 200, not a 404. The function did not check that the record exists, so a bogus GUID (or a deleted record) simply yields zero changes and the audit_configuration diagnosis below. A successful empty answer is therefore never proof that the record is there.
a VALID id with the WRONG (singular) entity set returns HTTP 404 [0x80060888] "Resource not found for the segment ''" — which NAMES the bad segment. That message is the singular-for-plural slip, not a missing record: fix the entity set name rather than hunting a deleted row. Do NOT read those two as an exhaustive split. On the sibling function RetrieveRecordChangeHistory, a sweep of 15 entity sets found a THIRD outcome — [0x80048d02] from a CORRECT plural entity set ('audits'), meaning the row really was absent, i.e. some entity sets DO validate the target. That sweep was run against the record-scoped function, not this one, so it is not confirmed here; it is reason enough to read the code that actually came back rather than trust a two-case rule. THE ALL-ZERO GUID IS HANDLED DIFFERENTLY BY THE TWO FUNCTIONS, live-confirmed. Passing 00000000-0000-0000-0000-000000000000 as record_id returns HTTP 200 with zero changes HERE, but the record-scoped dataverse_retrieve_record_change_history rejects the same id with HTTP 400 [0x80040203] "Expected non-empty Guid." That is Dataverse's own inconsistency between the two messages, not this server's. So an empty, successful answer from this tool can mean the caller passed a placeholder id — check the id before reading zero changes as a fact about the record.
AN EMPTY RESULT IS AMBIGUOUS, AND THIS TOOL RESOLVES IT. Audit rows are written only where auditing is enabled at organization AND table AND column level, so zero changes cannot by itself distinguish "nothing ever changed" from "auditing was never switched on". ONLY when there are zero changes, three probes fire concurrently and an audit_configuration block is attached carrying organization_audit_enabled / table_audit_enabled / column_audit_enabled and a diagnosis naming the OUTERMOST disabled level:
auditing_off_at_organization / auditing_off_at_table / auditing_off_at_column
auditing_enabled_no_changes_recorded — all three on, so the empty answer is real (auditing still only records changes made after it was switched on)
undetermined — a probe failed or returned an unreadable shape; the level is reported as null with the reason in probe_errors, and NOTHING is guessed.
NOT EVERY ENTRY IS A RESULT. Dataverse MAY add org-level audit-CONFIGURATION rows (auditing itself switched on or off) to a response. They arrive when an audit-configuration change falls inside the TARGET RECORD'S history window, so their presence and count VARY BY TARGET — a record created after the last such change gets none, while older records on the same org got four each, live-measured. audit_configuration_events_count: 0 is a normal, expected answer. They can arrive anywhere in the list and are identified by their SHAPE — no @odata.type, AuditRecord and nothing else, and an all-zero AuditRecord._objectid_value — never by their position. They are split out into audit_configuration_events (with audit_configuration_events_count) and are NOT counted as changes: audit_details, count and has_more cover this column's own changes only.
ENTRIES ARE POLYMORPHIC. Each change is returned verbatim, so read its @odata.type: a column-scoped call is expected to yield AttributeAuditDetail (AuditRecord, OldValue, NewValue, InvalidNewValueAttributes, plus AuditRecord.attributemask naming the changed columns) but nothing guarantees it — AuditDetail has several subtypes. An entry with an UNRECOGNIZED @odata.type is reported as a change, never quietly dropped; only the typeless AuditRecord-only shape with an all-zero objectid is treated as configuration. detail_types counts the @odata.type values actually present on the returned page, and unclassified_typeless_count reports how many entries carrying NO @odata.type were kept as changes — 0 on every response observed so far, and a non-zero value means this tool met an entry it could not name rather than that anything was lost.
RESPONSE SHAPE IS CHECKED, NOT ASSUMED. The entries sit TWO levels down (AuditDetailCollection -> AuditDetails). If that container is absent or is not a list, the tool returns normalized: false with the raw body — a missing container is NOT reported as "no changes".
PagingInfo is not sent, so the changes are trimmed client-side to top and has_more reports whether anything was cut. total_record_count appears ONLY when Dataverse supplied a real count: it is live-confirmed to arrive as -1 here both with and without PagingInfo, and a negative count is suppressed rather than passed on. It is never substituted with the number of returned entries.
URL form: GET /api/data/v9.2/RetrieveAttributeChangeHistory( Target=@t,AttributeLogicalName=@a) ?@t={"@odata.id":"accounts()"}&@a='name'
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| params | Yes |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| result | Yes |