---
title: "List Current Contract Terms"
method: GET
path: "/contracts/current-terms"
tags: ["contracts"]
---

# List Current Contract Terms

`GET /contracts/current-terms`

The term in force on every contract in the company, one row per contract.

Declared above ``GET /{contract_id}`` for the same reason ``/renewals`` is.
Read-only, gated by router auth only, and unfiltered and unpaged — the surface
reading it filters, sorts and pages client-side. Unlike ``/renewals`` it keeps
decided and open-ended terms.

My Services' contracts tab reads the same rows narrowed to the caller's own
book (``GET /vrm/my-contracts``); both build them with
``build_current_term_rows``, so the two cannot describe a term differently.

The term each row carries is the one the contract is *in*, not the tail of its
chain (``current_terms_for_company``). The cadence is resolved once here
because both halves need it: selecting the term consults it, and serializing
reports ``attention_due`` from it.

## Response `200`

Successful Response

- ContractCurrentTermRowResponse[]
  - `contract_id` string, uuid, required
  - `contract_title` string, required
  - `contract_status` 'draft' | 'active' | 'expired' | 'terminated', required — ``DRAFT`` and ``TERMINATED`` are human judgments; the derivation in ``recompute_contract_status`` overwrites neither.
  - `vendor_id` string, uuid, nullable
  - `vendor_name` string, nullable
  - `vendor_website` string, nullable
  - `service_names` string[]
  - `term` ContractTermResponse — One term period, with the state and countdown derived server-side. ``state`` / ``status`` / ``decision_deadline`` / ``deadline_kind`` / ``days_remaining`` are computed, never stored — the table, the email, and this response all read them from the same helpers so they cannot disagree.
    - `term_id` string, uuid, required
    - `contract_id` string, uuid, required
    - `term_number` integer, required
    - `previous_term_id` string, uuid, nullable
    - `start_date` string, date, nullable
    - `end_date` string, date, nullable
    - `termination_notice_date` string, date, nullable
    - `notice_date_source` 'derived' | 'extracted' | 'manual' — Where a term's ``termination_notice_date`` came from. Drives whether moving ``end_date`` recomputes the notice date: only ``DERIVED`` dates are recomputed, so a human-entered or document-stated date is never silently overwritten.
    - `notice_period_value` integer, nullable
    - `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``).
    - `renewal_term_months` integer, nullable
    - `auto_renews` boolean, nullable
    - `decision` 'renew' | 'renegotiate' | 'terminate' | 'replace' — What a user decided to do about a term coming up for renewal. A **fixed** set, not a per-company configurable one: it is what lets the email, the API and the state machine name the same thing without snapshotting a label onto every term. Two behavior pairs (RENEW/RENEGOTIATE, TERMINATE/REPLACE) — same effect, different record.
    - `decision_label` string, nullable
    - `decision_reason` string, nullable
    - `decision_source` 'user' | 'backfill' — Who recorded the decision. No ``SYSTEM_*`` member, deliberately: **nothing but a person records a decision**. A term that runs past its end date undecided stays ``OVERDUE`` and gets surfaced rather than auto-renewed on the customer's behalf — an invented decision is indistinguishable from one they made, and the renewal history is what customers audit.
    - `decided_by_user_id` string, uuid, nullable
    - `decided_by_label` string, nullable
    - `decided_at` string, date-time, nullable
    - `actioned_at` string, date-time, nullable
    - `actioned_by_user_id` string, uuid, nullable
    - `actioned_by_label` string, nullable
    - `state` 'open' | 'overdue' | 'decided' | 'renewed' | 'ended', required — A term's state, **derived** — never stored. Every value is a function of the dates and the decision, so a stored copy would have to be rewritten on each transition and would drift. The predicates live in ``backend.helpers.contract_terms.term_state``.
    - `status` 'overdue' | 'past_notice' | 'pending_decision' | 'pending_action' | 'active' | 'terminated' | 'replaced' | 'renewed' | 'renegotiated', required — Where a term stands, in the one phrase a surface shows for it. A different question from ``ContractTermState``, which is the state machine's own vocabulary. This one tells the **two lapsed dates apart** — a notice window that closed under a term still running is a different problem from a term that ran out entirely — and names outstanding work in the two halves somebody acts on. ``term_status`` derives it; nothing stores it. Declared **most urgent first**, and sorted on that order rather than on the labels, so ascending puts the work at the top of a list.
    - `decision_deadline` string, date, nullable
    - `deadline_kind` 'notice' | 'renewal' — Which of a term's two dates a reminder counts down to. A term has two real deadlines, not one derived deadline: the date you must give notice by, and the date the agreement turns over. Each gets its own ladder off its own cadence, because they are different distances from "act now" — a notice deadline wants months of warning, a renewal date wants days. Who receives a row is **not** encoded here: both configured audiences get every row, resolved at send time (``resolve_term_assignees``).
    - `days_remaining` integer, nullable
    - `in_effect` boolean
    - `attention_due` boolean
    - `notice_reminders_enabled` boolean
    - `renewal_reminders_enabled` boolean
    - `next_notice_reminder_date` string, date, nullable
    - `next_renewal_reminder_date` string, date, nullable
    - `notifications` ContractTermNotificationResponse[]
      - `notification_id` string, uuid, required
      - `lead_days` integer, required
      - `scheduled_for` string, date, required
      - `deadline_kind` 'notice' | 'renewal', required — Which of a term's two dates a reminder counts down to. A term has two real deadlines, not one derived deadline: the date you must give notice by, and the date the agreement turns over. Each gets its own ladder off its own cadence, because they are different distances from "act now" — a notice deadline wants months of warning, a renewal date wants days. Who receives a row is **not** encoded here: both configured audiences get every row, resolved at send time (``resolve_term_assignees``).
      - `target_date` string, date, required
      - `status` 'pending' | 'sent', required — Two values only: cancelling a reminder deletes the unsent row. ``AnnualKeyDates`` has three overlapping "off" states because it kept cancelled rows around. The audit of *why* reminders stopped lives on the term (``decided_at`` / ``decided_by_user_id``), not here.
      - `sent_at` string, date-time, nullable
    - `created` string, date-time, nullable
    - `modified` string, date-time, nullable

---

[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/425d5b8a3c17/schema)
