Skip to main content
The Diff API lets you compare any two snapshots for the same subject and see precisely what changed between them. Tally computes the diff as a set of RFC 6902 JSON Patch operationsadd, remove, and replace — applied to the envelope’s attribute and subject fields. You choose the scope of the comparison (all fields, only attributes, or the full envelope) and whether to include evidence attribution for the changed paths. A change_summary object gives you operation counts at a glance, and you can request cryptographic verification of both source snapshots in the same call.

GET /v1/subjects/:subject_type/:subject_id/diff

Compare two snapshots for a subject by their integer version numbers.
GET /v1/subjects/:subject_type/:subject_id/diff?from_version=1&to_version=3
GET /v1/tenants/:tenant_id/subjects/:subject_type/:subject_id/diff?from_version=1&to_version=3

Path parameters

subject_type
string
required
Must be "entity" or "individual".
subject_id
string
required
The subject’s stable identifier, e.g. "ent_acme_001".

Query parameters

ParameterTypeDefaultDescription
from_versionintegerRequired. The snapshot version to diff from (the “before” state). Must be ≥ 1.
to_versionintegerRequired. The snapshot version to diff to (the “after” state). Must be ≥ 1.
formatstringrfc6902Diff output format. Currently only "rfc6902" is supported.
includestring/subject,/attributesComma-separated JSON Pointer roots that scope the diff. Allowed values: /subject, /attributes, / (full envelope). Multiple roots are comma-separated, e.g. include=/subject,/attributes.
include_attributionstringchanged_onlyWhether to include evidence attribution for changed paths. "changed_only" adds an attribution array; "none" omits it.
verifystringnoneVerification mode for both source snapshots: "none", "hash", or "chain".

GET /v1/snapshots/:from_snapshot_id/diff/:to_snapshot_id

Compare two snapshots by their UUIDs. Both snapshots must belong to the same subject; Tally returns 400 validation_error if they don’t.
GET /v1/snapshots/:from_snapshot_id/diff/:to_snapshot_id
GET /v1/tenants/:tenant_id/snapshots/:from_snapshot_id/diff/:to_snapshot_id

Path parameters

from_snapshot_id
string
required
UUID of the snapshot to diff from (the “before” state).
to_snapshot_id
string
required
UUID of the snapshot to diff to (the “after” state). Must be a UUID for the same subject as from_snapshot_id.

Query parameters

Same as the version-based endpoint above, minus from_version and to_version.
ParameterTypeDefaultDescription
formatstringrfc6902Diff output format. Only "rfc6902" is supported.
includestring/subject,/attributesScope of the diff.
include_attributionstringchanged_onlyWhether to annotate changed paths with evidence attribution.
verifystringnoneVerification mode: "none", "hash", or "chain".

Response shape

Both diff endpoints return the same response structure.
subject
object
Present on the version-based endpoint. Contains subject_type and subject_id.
format
string
Always "rfc6902" in the current API version.
from
object
References the “before” snapshot: snapshot_id and snapshot_version.
to
object
References the “after” snapshot: snapshot_id and snapshot_version.
include
array
Sorted array of JSON Pointer roots that scoped this diff, e.g. ["/attributes", "/subject"].
ops
array
Array of RFC 6902 patch operations. Each operation has op ("add", "remove", or "replace"), path (a JSON Pointer string), and value (present for add and replace operations).
ops_hash
string
A deterministic hash of the ops array. Use this to confirm that two diff responses for the same snapshot pair produce identical operations.
change_summary
object
Aggregated operation counts.
attribution
array
Present when include_attribution=changed_only (the default). An array of objects, one per distinct changed path, each containing:
  • path — the JSON Pointer path
  • from — the evidence references from the from snapshot’s attribute_paths, or null
  • to — the evidence references from the to snapshot’s attribute_paths, or null
verification
object
Present when verify is not "none". Contains mode, hash, and chain sub-objects covering both the from and to snapshots.

Example: relationship ownership change

The following diff compares version 3 and version 4 of ent_acme_001. Between those versions, a beneficial owner’s ownership_percent was updated from 35 to 40, and a new last_reviewed field was added to the relationship.
curl "https://api.tally.io/v1/subjects/entity/ent_acme_001/diff?from_version=3&to_version=4&include_attribution=none"
{
  "subject": {
    "subject_type": "entity",
    "subject_id": "ent_acme_001"
  },
  "format": "rfc6902",
  "from": {
    "snapshot_id": "3f0b2c2c-2e46-4b58-8c45-3b5c58f4e9b2",
    "snapshot_version": 3
  },
  "to": {
    "snapshot_id": "7e1a4f22-9b3c-4d58-bc12-6a0e1f2c3d4e",
    "snapshot_version": 4
  },
  "include": ["/attributes", "/subject"],
  "ops": [
    {
      "op": "replace",
      "path": "/attributes/relationships/0/ownership_percent",
      "value": 40
    },
    {
      "op": "add",
      "path": "/attributes/relationships/0/last_reviewed",
      "value": "2026-02-20"
    }
  ],
  "ops_hash": "f4a2c1b3e5d6a789b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3",
  "change_summary": {
    "total_ops": 2,
    "adds": 1,
    "removes": 0,
    "replaces": 1,
    "paths_changed": 2
  }
}

Example: diff with attribution

When you leave include_attribution at its default (changed_only), Tally appends an attribution array that maps each changed path to the evidence items recorded in both snapshots.
curl "https://api.tally.io/v1/subjects/entity/ent_acme_001/diff?from_version=3&to_version=4"
{
  "subject": {
    "subject_type": "entity",
    "subject_id": "ent_acme_001"
  },
  "format": "rfc6902",
  "from": {
    "snapshot_id": "3f0b2c2c-2e46-4b58-8c45-3b5c58f4e9b2",
    "snapshot_version": 3
  },
  "to": {
    "snapshot_id": "7e1a4f22-9b3c-4d58-bc12-6a0e1f2c3d4e",
    "snapshot_version": 4
  },
  "include": ["/attributes", "/subject"],
  "ops": [
    {
      "op": "replace",
      "path": "/attributes/relationships/0/ownership_percent",
      "value": 40
    },
    {
      "op": "add",
      "path": "/attributes/relationships/0/last_reviewed",
      "value": "2026-02-20"
    }
  ],
  "ops_hash": "f4a2c1b3e5d6a789b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3",
  "change_summary": {
    "total_ops": 2,
    "adds": 1,
    "removes": 0,
    "replaces": 1,
    "paths_changed": 2
  },
  "attribution": [
    {
      "path": "/attributes/relationships/0/last_reviewed",
      "from": null,
      "to": [
        {
          "evidence_id": "ev_2026_0201",
          "evidence_type": "ownership_update_attestation"
        }
      ]
    },
    {
      "path": "/attributes/relationships/0/ownership_percent",
      "from": [
        {
          "evidence_id": "ev_2026_0002",
          "evidence_type": "beneficial_ownership_attestation"
        }
      ],
      "to": [
        {
          "evidence_id": "ev_2026_0201",
          "evidence_type": "ownership_update_attestation",
          "role": "primary"
        }
      ]
    }
  ]
}

Example: diff with hash verification

Pass verify=hash to ask Tally to recompute and validate the envelope hash for both the from and to snapshots inline with the diff response.
curl "https://api.tally.io/v1/snapshots/3f0b2c2c-2e46-4b58-8c45-3b5c58f4e9b2/diff/7e1a4f22-9b3c-4d58-bc12-6a0e1f2c3d4e?verify=hash&include_attribution=none"
{
  "format": "rfc6902",
  "from": {
    "snapshot_id": "3f0b2c2c-2e46-4b58-8c45-3b5c58f4e9b2",
    "snapshot_version": 3
  },
  "to": {
    "snapshot_id": "7e1a4f22-9b3c-4d58-bc12-6a0e1f2c3d4e",
    "snapshot_version": 4
  },
  "include": ["/attributes", "/subject"],
  "ops": [
    {
      "op": "replace",
      "path": "/attributes/relationships/0/ownership_percent",
      "value": 40
    },
    {
      "op": "add",
      "path": "/attributes/relationships/0/last_reviewed",
      "value": "2026-02-20"
    }
  ],
  "ops_hash": "f4a2c1b3e5d6a789b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3",
  "change_summary": {
    "total_ops": 2,
    "adds": 1,
    "removes": 0,
    "replaces": 1,
    "paths_changed": 2
  },
  "verification": {
    "mode": "hash",
    "chain_supported": true,
    "hash": {
      "alg": "sha-256",
      "from": {
        "value": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
        "stored": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
        "valid": true
      },
      "to": {
        "value": "d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5",
        "stored": "d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5",
        "valid": true
      }
    },
    "chain": {
      "prev_hash": null,
      "valid": null
    }
  }
}

Error responses

HTTP statusError codeWhen it occurs
400validation_errorMissing or invalid from_version / to_version, invalid format, include, include_attribution, or verify value, or the two snapshot IDs belong to different subjects.
404not_foundOne or both of the requested snapshot versions (or IDs) do not exist for the subject.
403forbiddenOn tenant-scoped paths: the authenticated principal does not hold read_diff access for the subject.