Skip to content

Entity versioning

Many Invora entities are versioned: every committed change produces a new immutable version, while a stable key identifies the entity across all of them. This powers two things — optimistic concurrency (safe concurrent writes) and point-in-time reads (see an entity as it was).

KeyVersion

A versioned entity carries a composite id:

{
  "id": {
    "key": "INV-001",
    "version": 3,
    "versionId": "9c8b7..."
  }
}
Field Meaning
key The logical identifier — constant across every version of the entity.
version A sequential number starting at 1, incremented on each committed change.
versionId A unique identifier for this specific version.

The first committed state is version: 1; the next is version: 2, and so on.

Optimistic concurrency (concurrencyStamp)

Every read of a mutable entity returns a concurrencyStamp. Pass it back on the next write (update / freeze / delete). If another write landed in between, the stamp no longer matches and the call fails with ABORTED (HTTP 409) — re-read, take the fresh stamp, and retry.

curl -X PUT https://gateway.invora.app/api/v2/documents/INV-001 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "concurrencyStamp": "a1b2c3d4",
    "mask": "content.invoice.documentCurrencyCode",
    "changes": { "content": { "invoice": { "documentCurrencyCode": {"value": "SAR"} } } }
  }'

On a stale stamp:

Response (409 ABORTED)
{
  "code": 10,
  "message": "the resource was modified by another request; re-read and retry"
}

See Error handling for the retry pattern.

Point-in-time reads (timeReference)

List endpoints accept a timeReference on the filter to read entities as they were, rather than their current state. Its two modes are alternatives — set one or the other, not both:

State as of a moment
{
  "filter": { "timeReference": { "referenceDate": "2026-01-01T00:00:00Z" } }
}
Full version history
{
  "filter": { "timeReference": { "historicalData": true } }
}
  • referenceDate — returns each matching entity as it existed at that instant.
  • historicalData — returns all versions over time; group by id.key in your application to reconstruct each entity's timeline.

See List & filtering for how timeReference composes with the rest of the filter.

Reading a specific version

Beyond list-level time travel, versioned services let you read a single entity's history directly:

  • Versioned get (GetRequestVersioned) — fetch one specific historical version by key + version.
  • Key-version query (QueryKeyVersion) — list the version history (the sequence of KeyVersions) for a key, so you can pick a version to fetch.
Response (version history excerpt)
{
  "items": [
    { "key": "INV-001", "version": 1, "versionId": "3a1f..." },
    { "key": "INV-001", "version": 2, "versionId": "7c4d..." },
    { "key": "INV-001", "version": 3, "versionId": "9c8b..." }
  ]
}