Skip to main content
This page is the single source of truth for how the Policy API thinks about time. Every transaction carries two dates that do different jobs, and it’s critical to get the distinction and relationship between them right. If you haven’t seen the transaction/delta/segment model yet, skim Concepts first — this page assumes you know what a delta and a segment are.

Two independent date axes

Every transaction moves along two independent axes:
  • effectiveDatewhere 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 termpolicyStartDate <= 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).
  • transactionTimestampwhen 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).
The distinction matters:
effectiveDatetransactionTimestamp
What it answers”Where in the policy term does this change apply?""When did we record this decision?”
AxisPolicy-term timeAudit / booking time
Can move backwardYes — backdate freely within the termNo — must move forward (see Monotonicity)
DefaultNone — you always supply itnow() if omitted
What it is NOTNot “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’s startDate must equal the transaction’s effectiveDate. (delta.startDate == effectiveDate)
The 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 different 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):
Per-segment delta startDate (2025-05-01) for path "policy.deductible" must equal transaction effectiveDate (2025-04-01)
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:
  1. when you decided (the booking timestamp),
  2. when the change’s range starts (the delta’s startDate), and
  3. some separate date when the change legally takes effect.
The API has the first two. It deliberately does not have the third — and not because of a missing feature. It’s because that third date doesn’t correspond to any real insurance semantic. A change always takes effect exactly when its range starts — the change is the start of the range, and 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).
In every case it’s the same single date — the moment the change begins to apply — never a take-effect date separate from where the range starts.
“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.
There is no meaningful in-between — no case where “the date it takes effect” and “the date its range starts” are different dates that both matter. Once you see that, the imagined two-dimensional grid (term-date × take-effect-date) collapses to one dimension. A change happens on the term, full stop; when you booked it is metadata on the audit trail, not a third coverage axis. (This is the bitemporal resolution the model is built on.) So the recurring question — “how do I make this change take effect on date X but have its range start on date Y?” — has no API surface, by design. Take-effect and range-start are the same date: endorse effective on the date the change should begin to apply, whether that’s in the past or the future.

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
{
  "cancellationDate": "2025-03-15",
  "transactionTimestamp": "2025-06-01T14:30:00.000Z",
  "fullTermPolicyBillingInfo": {
    "policyPremium": 21000, "policyTaxes": 1050,
    "policyFees": 500, "policyGrandTotal": 22550
  }
}
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
{
  "effectiveDate": "2025-08-01",
  "transactionTimestamp": "2025-06-01T09:00:00.000Z",
  "deltas": [
    {
      "startDate": "2025-08-01",
      "endDate": "2025-12-31",
      "path": "policy.additionalExposures[id = 'exp-1'].bedCount",
      "action": "Overwrite",
      "value": 140
    }
  ]
}
Note 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
{
  "effectiveDate": "2025-03-01",
  "transactionTimestamp": "2025-06-01T11:00:00.000Z",
  "deltas": [
    {
      "startDate": "2025-03-01",
      "endDate": "2025-12-31",
      "path": "policy.additionalExposures",
      "action": "Add",
      "value": {
        "id": "exp-2",
        "exposureType": "OutpatientClinic",
        "facilityName": "Greenfield West Clinic",
        "bedCount": 0
      }
    }
  ]
}
The exposure’s range starts March 1; the “we decided in June” fact lives entirely in 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 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 rangepolicyType
[policyStart, Mar 31]claims-made
[Apr 1, policyEnd]occurrence
The April-onward segment’s 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 rangepolicyTypepolicyTypeRetroactiveApplication
[policyStart, Mar 31]claims-madenull
[Apr 1, policyEnd]occurrenceStart
This is a domain-modeling pattern, not a built-in framework feature. There is no policyTypeRetroactiveApplication field that ships with the API — it’s an illustration of a field a tenant would define for the rare lines where they need it. The framework does not enumerate retroactive-bearing fields in advance. Whether a value reaches outside its segment is a property of that field’s semantics, expressed with ordinary configured fields, never with a hidden second effective-date axis.
This is why the “third date” never materializes: the genuinely hard cases — a value bearing on dates outside its range — are handled by more fields inside the segment, leaving the temporal model itself one-dimensional.

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 sequential policyVersion, 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 set transactionTimestamp 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):
transactionTimestamp (2025-05-01T00:00:00.000Z) is earlier than the latest existing transaction on this policy (2025-06-01T14:30:00.000Z)
This constrains the audit axis only — the order in which decisions were booked. Effective dates may still backdate freely: a transaction booked today can carry an 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.