Two independent date axes
Every transaction moves along two independent axes:effectiveDate— where on the policy term a change lands. For an endorsement, it’s when the new coverage begins; for a cancellation, when coverage ends. It must fall within the policy term —policyStartDate <= effectiveDate <= policyEndDate, both bounds inclusive. It can sit anywhere in that window: in the past (a backdated correction) or the future (a change booked ahead of time).transactionTimestamp— when the decision was booked. This is the audit / booking axis — the wall-clock moment the change entered the system. It defaults to “now,” but you can set it explicitly when importing history (e.g. aligning to a bordereau booking date).
effectiveDate | transactionTimestamp | |
|---|---|---|
| What it answers | ”Where in the policy term does this change apply?" | "When did we record this decision?” |
| Axis | Policy-term time | Audit / booking time |
| Can move backward | Yes — backdate freely within the term | No — must move forward (see Monotonicity) |
| Default | None — you always supply it | now() if omitted |
| What it is NOT | Not “when we found out” | Not “when coverage changes” |
effectiveDate and transactionTimestamp move on independent axes. effectiveDate may backdate or post-date freely within the term; transactionTimestamp is the audit axis and only moves forward. A transaction booked today (a forward-moving transactionTimestamp) can carry an effectiveDate weeks in the past.The effective-date rule
There is exactly one rule binding a change to where it lands on the term:A per-segment delta’sThe plain-language why: the change is the start of the range. An endorsement’s “effective date” and “the date the new value starts applying” are not two different facts that happen to coincide — they are the same fact by construction. The effective date is the left edge of every range the transaction writes. Because of this, a single transaction cannot mix deltas with differentstartDatemust equal the transaction’seffectiveDate. (delta.startDate == effectiveDate)
startDates — they all start at the one effectiveDate. If two changes need to start on different dates, that’s two transactions. (Each delta still carries its own endDate, so changes in one transaction can have different durations — bump a limit for the rest of the term and add an insured for the summer only — they just all start at the effective date.)
A delta whose startDate doesn’t match the transaction’s effectiveDate is rejected with 400 (InvalidDelta):
Two exemptions, both server-internal, neither caller-facing.
fullTermDeltas (changes to fullTermPolicyInfo) carry no dates — they always span the whole term, and the endpoint forces their startDate to the policy start. And the cancel/reinstate cancellationEffectiveOnDate marker is written uniformly across the whole term by the server, so it’s exempt too. You never author either of these as a dated per-segment delta, so the rule above is the one you work with.No independent take-effect date
You cannot set a “take-effect” date that is independent of where the change’s range starts. There is no third time axis. It is tempting to imagine three dates:- when you decided (the booking timestamp),
- when the change’s range starts (the delta’s
startDate), and - some separate date when the change legally takes effect.
effectiveDate is that one date. Where it lands on the term doesn’t change the rule, only where that single date sits:
- Past — a backdated correction, or coverage agreed to apply from an earlier date.
- Present — a change taking effect today.
- Future — a change booked ahead of time (usually an agreed-upon change, but potentially a forward-looking correction).
“Correction” vs. “change” is a business distinction, not an API one. A correction makes the policy reflect what it should have been — typically backdated, because a value was wrong or a change was agreed earlier but booked late. A change is a genuine evolution of the contract from its effective date onward. The API models both identically: an endorsement with an
effectiveDate.Worked examples
Backdate: a cancellation discovered late
The insured’s coverage actually ended March 15, but nobody told you until June 1. The cancellation’s effective date is the true past date; the booking timestamp is now.POST /v1/policies/{policyId}/transaction/cancel
effectiveDate (the cancellation date) is March 15 — backdated. transactionTimestamp is June 1 — the audit axis recording when you booked it. The policy reads as cancelled from March 15 onward, and the audit trail shows the decision was made June 1.
Book ahead: a future-effective endorsement
A coverage change is agreed today (June 1) but doesn’t begin until August 1. The effective date is in the future; the booking timestamp is now.POST /v1/policies/{policyId}/transaction/endorse
startDate equals effectiveDate (the one rule). The change starts August 1 and runs to term end; it was booked June 1. Nothing “activates” later than its range start — the range start is when it takes effect.
”Add Exposure 2 as though March 1, decided June 1”
You decide on June 1 to add an exposure, and it should have been on the policy since March 1. The instinct is to look for a way to decouple the dates — “set it live as of March 1 but mark that we only decided in June.” You don’t. You endorse it as a retroactive correction: a normal endorsement whose range starts March 1.POST /v1/policies/{policyId}/transaction/endorse
transactionTimestamp and the version history. There is no separate field for it because there is no separate axis for it.
The one wrinkle: what if it’s not the whole change that should reach back, but only the legal bearing of certain fields? That’s the next section.
Retroactive legal bearing
Retroactive legal bearing beyond a change’s effective range is modeled with tenant-defined fields, not a separate effective-date axis.Sometimes a value has legal consequences for dates outside the segment it lives in. The canonical example is
policyType on an occurrence vs. claims-made form. Suppose an underwriter switches policyType from claims-made to occurrence effective April 1, intending the occurrence terms to apply retroactively to incidents that predate April 1.
You model this exactly as you’d expect from the rule above — endorse effective April 1, producing two segments:
| Segment range | policyType |
|---|---|
[policyStart, Mar 31] | claims-made |
[Apr 1, policyEnd] | occurrence |
occurrence value has bearing on incident dates before its own range. The segment’s date range tells you the policy window; it does not, by itself, tell you how far the terms inside it reach. That “reach-back” is a property of the field’s meaning, captured by an additional field the tenant configures into their own schema — for instance a value like policyTypeRetroactiveApplication set to Start on that segment, declaring that the occurrence value evaluates claims since the policy start:
| Segment range | policyType | policyTypeRetroactiveApplication |
|---|---|---|
[policyStart, Mar 31] | claims-made | null |
[Apr 1, policyEnd] | occurrence | Start |
Precedence and monotonicity
Two final rules govern how the two clocks interact across an entire policy history.Cross-transaction precedence — latest version wins on overlap
A single transaction’s deltas are applied to the segments that overlap them. Across the policy’s whole history, later transactions win on overlap. Each transaction produces a version with a sequentialpolicyVersion, and that ordinal is the tiebreaker: when more than one transaction has written the same path over the same term-date, the resolved value is the one from the highest policyVersion. The system materializes state by folding transactions forward in policyVersion order, so reading the resolved value at a date is the dual — walk the history newest-first and take the first writer that addresses that (path, date).
This is the deliberate precedence model, not an accident of storage order, and it is exactly what makes backdated corrections work: a later transaction with a lower effectiveDate can overwrite the value an earlier transaction set for the same date range, because the later transaction has the higher version. There is no per-transaction “priority” field — insertion order (policyVersion) is the sole arbiter of overlap.
Within a single transaction, overlap is forbidden, not resolved. Newest-wins only orders deltas across transactions. Two deltas in the same transaction that target the same path (or an ancestor/descendant of it) have no version ordering between them, so the API rejects them up front (see Within-Transaction Path Conflicts).
Transaction timestamp monotonicity
When you settransactionTimestamp explicitly, it must be >= the largest transactionTimestamp already recorded on that policy. The audit axis only moves forward. An out-of-order timestamp is rejected (400, InvalidRequest):
effectiveDate weeks in the past (that’s the backdate example above). Omit transactionTimestamp to default to the current time, which is monotonic by construction and skips this check.
This is the symmetry at the heart of the temporal model: transactionTimestamp can’t go backwards; effectiveDate backdates freely. One axis records the unalterable order in which you learned and decided things; the other places each change wherever it belongs on the policy term.