---
title: "Update Contract Lifecycle Settings"
method: PUT
path: "/contract-lifecycle-settings"
tags: ["contract-lifecycle-settings"]
---

# Update Contract Lifecycle Settings

`PUT /contract-lifecycle-settings`

Replace the company's renewal reminder policy with the body.

**The first save is what starts the reminders**: until this row exists the
company schedules nothing (``desired_term_notifications``).

**Existing terms are rescheduled, not just future ones**, because reminder
rows are materialized when a term is written. Sent rows are untouched and
terms with no date still ahead are left alone — see
``resync_company_term_notifications``. Turning a date off is the same write.

Who is reminded takes effect without any resync: recipients are resolved at
send time.

## Request body

- ContractLifecycleConfigurationUpdate — The company's renewal reminder policy, as the settings page sends it. Every field is required: this is the whole policy, not a patch, so a save from a stale page cannot half-apply. The two ``default_*`` columns on the table are **absent on purpose** — nothing reads them yet (see ``upsert_contract_lifecycle_settings``), and a request field nothing acts on is a promise the product does not keep. Two cadences and two recipient roles, on independent axes. The cadences belong to the term's two *dates* — a notice deadline wants months of warning, a renewal date wants days — and each date carries a switch that turns its ladder off for the whole company. The roles are the two groups of people reminded on both, for the life of the term (unioned at send time, not stored — see ``resolve_term_assignees``): the assignees own the renewal decision, the subscribers are kept in the loop. The switches are separate fields rather than an empty cadence: the ladder is bounded at one lead day minimum, so an empty list would be a second way to say "off" that the CHECK rejects — and a company turning a date back on would have lost the schedule it had. Each role is a *set*, so a company can name two teams and a person for one of them. An empty role is a role nobody fills: it resolves to nobody rather than falling back, which is why the settings page blocks the save. Which id belongs with which mode — and whether the mode is in a contract's scope — is checked by the endpoint against the company's directory, since that needs a database.
  - `notice_reminder_lead_days` integer[], required
  - `renewal_reminder_lead_days` integer[], required
  - `notice_reminders_enabled` boolean, required
  - `renewal_reminders_enabled` boolean, required
  - `assignees` AssigneeSelectionInput[], required
    - `mode` 'vendor_assignees' | 'user' | 'team', required — How responsibility for an entity is resolved to a set of users. Not every mode is valid for every entity — see ``ASSIGNEE_SCOPES``.
    - `user_id` string, uuid, nullable
    - `team_id` string, uuid, nullable
  - `subscribers` AssigneeSelectionInput[], required
    - `mode` 'vendor_assignees' | 'user' | 'team', required — How responsibility for an entity is resolved to a set of users. Not every mode is valid for every entity — see ``ASSIGNEE_SCOPES``.
    - `user_id` string, uuid, nullable
    - `team_id` string, uuid, nullable

## Response `200`

Successful Response

- ContractLifecycleConfigurationResponse — The company's resolved lifecycle settings. Always concrete values: an unconfigured company gets the code defaults with ``is_configured=False``, so the UI can render "using defaults" without null-branching every field.
  - `is_configured` boolean, required
  - `notice_reminder_lead_days` integer[], required
  - `renewal_reminder_lead_days` integer[], required
  - `notice_reminders_enabled` boolean
  - `renewal_reminders_enabled` boolean
  - `default_notice_period_value` integer, nullable
  - `default_notice_period_unit` 'days' | 'months' — Unit of a termination-notice period. Months are not 30 days: 3 months before 2026-05-31 is 2026-02-28, while 90 days before it is 2026-03-02. This generates a legal deadline, so the unit is stored rather than normalized to days (see ``backend.helpers.contract_terms.derive_notice_date``).
  - `default_renewal_term_months` integer, required
  - `assignees` AssigneeSelectionResponse[]
    - `mode` 'vendor_assignees' | 'user' | 'team', required — How responsibility for an entity is resolved to a set of users. Not every mode is valid for every entity — see ``ASSIGNEE_SCOPES``.
    - `user_id` string, uuid, nullable
    - `team_id` string, uuid, nullable
    - `label` string, required
  - `subscribers` AssigneeSelectionResponse[]
    - `mode` 'vendor_assignees' | 'user' | 'team', required — How responsibility for an entity is resolved to a set of users. Not every mode is valid for every entity — see ``ASSIGNEE_SCOPES``.
    - `user_id` string, uuid, nullable
    - `team_id` string, uuid, nullable
    - `label` string, required
  - `available_assignee_modes` AssigneeMode[]
  - `default_assignee_mode` 'vendor_assignees' | 'user' | 'team', required — How responsibility for an entity is resolved to a set of users. Not every mode is valid for every entity — see ``ASSIGNEE_SCOPES``.

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.dev/kobaltlabs/apis/fastapi.md) · [All operations](https://skmtc.dev/kobaltlabs/apis/fastapi/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/kobaltlabs/fastapi/revisions/3ff4c0bd276b/schema)
