Skip to content

Concurrent edits & data consistency

This guide explains how the CRM behaves when more than one person — or a person and an import / integration — changes the same data at once. It is written for workspace members and admins.

If you only remember one thing:

By default, the last successful save wins on a given field — like a shared spreadsheet. Clients that care (record detail, careful API/AI integrations) can ask the server to reject a save if someone else changed the record first.


This product is a step up from shared Excel: real uniqueness and booking-style “no overlap” rules, permissions, and an audit trail of who called the API.

It is not a banking ledger. We do not promise immutable journals, cryptographic proof chains, or automatic merging of every conflicting edit.


  • Each cell save updates only that field.
  • If you and a colleague edit different fields, both changes usually keep.
  • If you both edit the same field, whichever save finishes last is what stays. The grid may flash when someone else’s change arrives.
  • This is intentional spreadsheet-style collaboration.
  • When you open a record, the app remembers when it was last updated.
  • On Save, it tells the server “only accept this if the record is still at that version.”
  • If a colleague (or an import / AI agent) saved first, you get an error that the record changed — your save is not applied. Reload, check their changes, then edit again.
  • Ordinary PUT / PATCH without a version check behave like last-write-wins.
  • Send optional expectedUpdatedAt (the updatedAt you last read) to get the same protection as the detail page. If the record moved on, the API returns 409 with code STALE_RECORD and changes nothing.
  • MCP update_record accepts the same idea as expected_updated_at.

When you import into an existing table you choose duplicate handling:

Mode Behaviour
append (default) Every row tries to insert. If a unique field collides, that row errors.
skip Rows that match an existing record on your match field are counted and not written.
update A single match overwrites the mapped columns on that record (unmapped columns keep their values). No match → insert. Ambiguous matches error.

Important limits:

  • Import does not use the “expected version” check. An import update can overwrite fields someone edited in the UI while the job ran — last writer wins on those mapped fields.
  • By default the job commits in batches. If a later batch fails or you cancel, earlier batches stay. Choose atomic (within the size limit) if you need all rows or none for that load.
  • Invalid rows can be skipped (skipInvalid) so the rest of the file still loads; the job result lists row errors.

Prefer pausing heavy UI editing on a table while a large update import runs, or use skip when you only want to add new rows.


Situation What you see
Unique value already taken Validation error (HTTP 400) naming the field / rule
Booking / range would overlap Validation error (HTTP 400) for the no-overlap rule
Detail/API save with stale version HTTP 409, code STALE_RECORD — reload and retry
Record deleted (or outside your access) since you loaded it HTTP 404
Not allowed after a role/permission change HTTP 403 (or 404 when the record is hidden from you)

Constraint checks are enforced for the UI, REST API, imports, and MCP — the same rules everywhere.


  • HTTP audit log (Workspace settings → audit, for roles that can view it): who called which mutating API, when, with a redacted request body. This always runs for /api mutations.
  • Application events: record updates can include a before and after snapshot (ItemUpdated), which is enough to see who overwrote which fields.

The audit trail answers “who submitted this change.” It does not by itself stop two people from saving in quick succession unless the client used expectedUpdatedAt.

Schema changes applied via a schema plan are all-or-nothing: if apply fails, nothing from that plan is left half-applied.


  • Default collaboration model is last-write-wins, not conflict-free replicated data types (CRDTs).
  • Imports that update matched rows will overwrite mapped fields without a per-field conflict dialog.
  • We are not a payments or general ledger system; do not treat record history as court-grade financial evidence without your own process around exports and retention settings.

For how permissions interact with “who can still save,” see Authorization & Permissions.

Open the app