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.
What we are (and are not)
Section titled “What we are (and are not)”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.
Two people edit the same record
Section titled “Two people edit the same record”Spreadsheet / grid
Section titled “Spreadsheet / grid”- 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.
Record detail page
Section titled “Record detail page”- 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.
API & AI (MCP) integrations
Section titled “API & AI (MCP) integrations”- Ordinary
PUT/PATCHwithout a version check behave like last-write-wins. - Send optional
expectedUpdatedAt(theupdatedAtyou last read) to get the same protection as the detail page. If the record moved on, the API returns 409 with codeSTALE_RECORDand changes nothing. - MCP
update_recordaccepts the same idea asexpected_updated_at.
Imports when records already exist
Section titled “Imports when records already exist”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
updatecan 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.
What errors look like
Section titled “What errors look like”| 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.
What the audit log can prove
Section titled “What the audit log can prove”- 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
/apimutations. - 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.
Honest limits
Section titled “Honest limits”- Default collaboration model is last-write-wins, not conflict-free replicated data types (CRDTs).
- Imports that
updatematched 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.