Skip to main content
Every identity record in Tally is stored as a versioned snapshot envelope — a self-describing JSON document that captures the full state of a subject at a point in time. When you submit a snapshot for the first time, Tally automatically establishes your tenant as the owner of that subject. Subsequent changes flow through a propose-and-apply workflow that produces an auditable, cryptographically linked chain of versions.

Submit your first snapshot (v1)

Send a POST request to /v1/tenants/:tenant_id/entity-states with the full envelope in the request body. For your first snapshot, set snapshot_version to 1 — Tally will reject a version greater than 1 if no prior version exists for the subject.
curl -X POST https://api.tally.io/v1/tenants/acme-kyc/entity-states \
  -H "Authorization: Bearer $TALLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "envelope_version": "entity_state_envelope_v1",
    "snapshot_id": "b1a2c3d4-e5f6-7890-abcd-ef1234567890",
    "snapshot_version": 1,
    "generated_at": "2026-02-18T16:12:00Z",
    "subject": {
      "subject_type": "entity",
      "subject_id": "ent_acme_001"
    },
    "attributes": {
      "legal_name": "Acme Industrial Supply, Inc.",
      "trade_names": ["Acme Supply"],
      "jurisdiction": "US-DE",
      "incorporation_date": "2008-05-12",
      "status": "active",
      "registered_address": {
        "line1": "1200 Market St",
        "city": "Wilmington",
        "region": "DE",
        "postal_code": "19801",
        "country": "US"
      }
    },
    "evidence": [
      {
        "evidence_id": "ev_2026_0001",
        "evidence_type": "government_registry_extract",
        "source": "Delaware Division of Corporations",
        "captured_at": "2026-02-18T16:10:00Z",
        "retrieved_at": "2026-02-18T16:12:00Z",
        "locator": "registry://de/corp/2288117",
        "hash": {
          "alg": "SHA-256",
          "value": "b3c1f1f0d9c06a8b3c2b9f4a2d5c1e9a0b5e1f5c8a7d9e0f1a2b3c4d5e6f7a8b"
        }
      }
    ],
    "audit": {
      "created_by": "kyc_service",
      "created_at": "2026-02-18T16:12:00Z",
      "source": "tally_ingest"
    }
  }'
A successful submission returns 201 Created with the stored envelope:
{
  "envelope_version": "entity_state_envelope_v1",
  "snapshot_id": "b1a2c3d4-e5f6-7890-abcd-ef1234567890",
  "snapshot_version": 1,
  "generated_at": "2026-02-18T16:12:00Z",
  "subject": {
    "subject_type": "entity",
    "subject_id": "ent_acme_001"
  },
  "attributes": { ... },
  "evidence": [ ... ],
  "audit": { ... }
}

Required envelope fields

FieldTypeDescription
envelope_versionstringMust be "entity_state_envelope_v1"
snapshot_idUUIDA UUID you generate that uniquely identifies this snapshot
snapshot_versioninteger1 for the first snapshot; must be monotonically increasing
generated_atRFC 3339 datetimeWhen this snapshot was generated
subject.subject_typestring"entity" or "individual"
subject.subject_idstringYour stable identifier for the subject
attributesobjectThe identity attributes for this subject
evidencearraySupporting evidence records (may be an empty array)
audit.created_bystringThe system or person that created this snapshot
audit.created_atRFC 3339 datetimeCreation timestamp
audit.sourcestringThe data source or ingestion system

Submit an individual snapshot

The envelope format is the same for individual subjects — only the subject_type value and the shape of attributes differ. Individual records typically carry identity attributes such as name, date of birth, and nationalities.
curl -X POST https://api.tally.io/v1/tenants/acme-kyc/entity-states \
  -H "Authorization: Bearer $TALLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "envelope_version": "entity_state_envelope_v1",
    "snapshot_id": "9a33c76e-0b08-4e90-b3fb-5e0f8f6c9a1d",
    "snapshot_version": 1,
    "generated_at": "2026-02-20T15:05:10Z",
    "subject": {
      "subject_type": "individual",
      "subject_id": "ind_jordan_lee_99"
    },
    "attributes": {
      "full_name": "Jordan Lee",
      "date_of_birth": "1987-11-04",
      "nationality": "US",
      "addresses": [
        {
          "line1": "88 Pine St",
          "city": "Philadelphia",
          "region": "PA",
          "postal_code": "19103",
          "country": "US"
        }
      ]
    },
    "evidence": [
      {
        "evidence_id": "ev_2026_0101",
        "evidence_type": "government_id",
        "source": "State DMV",
        "captured_at": "2026-02-10T11:45:00Z",
        "hash": {
          "alg": "SHA-256",
          "value": "1f2e3d4c5b6a79808796a5b4c3d2e1f0a1b2c3d4e5f60718293a4b5c6d7e8f90"
        },
        "locator": "vault://evidence/ev_2026_0101"
      }
    ],
    "audit": {
      "created_by": "kyc_service",
      "created_at": "2026-02-20T15:05:10Z",
      "source": "tally_ingest"
    }
  }'

Updating a snapshot (propose + apply)

Tally uses a two-step propose-and-apply workflow for incremental updates. Rather than submitting a full new snapshot, you describe the change as an RFC 6902 JSON Patch document. Tally validates the patch, derives the new snapshot ID deterministically, and creates the next version in a single atomic operation.
1
Step 1 — Propose the update
2
Send a POST to /v1/tenants/:tenant_id/entity-state-updates with the subject ID, the current snapshot as the base, and your patch operations.
3
curl -X POST https://api.tally.io/v1/tenants/acme-kyc/entity-state-updates \
  -H "Authorization: Bearer $TALLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "subject_id": "ent_acme_001",
    "base_snapshot_id": "3f0b2c2c-2e46-4b58-8c45-3b5c58f4e9b2",
    "base_snapshot_version": 3,
    "patch": [
      {
        "op": "test",
        "path": "/attributes/relationships/0/ownership_percent",
        "value": 35
      },
      {
        "op": "replace",
        "path": "/attributes/relationships/0/ownership_percent",
        "value": 40
      },
      {
        "op": "add",
        "path": "/attributes/relationships/0/last_reviewed",
        "value": "2026-02-20"
      }
    ]
  }'
4
The response includes an update_id you use in the next step:
5
{
  "update_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567891"
}
6
Step 2 — Apply the update
7
Send a POST to /v1/tenants/:tenant_id/entity-state-updates/:update_id/apply. Tally applies the patch to the latest snapshot, validates the result, computes a new deterministic snapshot ID, and writes the new version.
8
curl -X POST https://api.tally.io/v1/tenants/acme-kyc/entity-state-updates/a1b2c3d4-e5f6-7890-abcd-ef1234567891/apply \
  -H "Authorization: Bearer $TALLY_API_KEY"
9
A 201 Created response returns the full new snapshot envelope with the incremented snapshot_version.
The patch example above is taken from the relationship-change example: ownership in ent_acme_001 moves from 35% to 40% and a last_reviewed date is added. The test operation at the top is optional but strongly recommended — it acts as an optimistic concurrency guard that causes the apply to fail if the value has already changed.

Direct snapshot submission (v2+)

If you prefer to manage the full envelope yourself — for example when migrating records from another system — you can skip the propose/apply flow and submit a complete snapshot directly to POST /v1/tenants/:tenant_id/entity-states with any snapshot_version value greater than 1.
curl -X POST https://api.tally.io/v1/tenants/acme-kyc/entity-states \
  -H "Authorization: Bearer $TALLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "envelope_version": "entity_state_envelope_v1",
    "snapshot_id": "c2d3e4f5-a6b7-8901-cdef-234567890abc",
    "snapshot_version": 2,
    "generated_at": "2026-02-20T14:25:30Z",
    "subject": {
      "subject_type": "entity",
      "subject_id": "ent_acme_001"
    },
    "attributes": { ... },
    "evidence": [ ... ],
    "audit": { ... }
  }'
When using direct submission for snapshot_version >= 2, you must compute the complete attributes object yourself — there is no automatic merge with the previous version. Your tenant must already be the registered owner of the subject (established when you submitted version 1).
snapshot_id must be globally unique. If you submit a request with a snapshot_id that already exists in Tally — even for a different subject — you will receive a 409 Conflict response. Always generate a fresh UUIDv4 for each direct submission, or use the propose/apply flow which derives the ID automatically.
Choose your ID generation strategy. Use UUIDv4 (random) for direct snapshot submissions where you want a simple, collision-resistant identifier. Use UUIDv5 (SHA-1 namespace hash) for deterministic IDs that can be reproduced from the same inputs — the updates API does this automatically by hashing base_snapshot_id + canonical_patch, which means re-applying the same patch always yields the same snapshot ID.