Add/Edit a Schedule
Add/Edit a Schedule
Why edit a schedule, what’s editable, and what locks it down
A schedule (UsageCommitmentSchedule) is a calendar-time phase of a commitment: its own startDate and endDate, commitmentInterval (billing cadence), and prepaymentScheduleType. Edit a schedule (rather than a step) when the change concerns when or how often billing happens, or which billing terms are eligible inputs to the steps in that phase, not the tier amounts themselves.
What’s editable on a schedule
Send a partial update to PATCH .../schedules/:scheduleId. All fields are optional; send only what’s changing.
endDate: new end date (inclusive). Only valid on the LAST schedule of the commitment (changing an earlier schedule’s end would break contiguity with the one after it).startDateChange:{ mode: "extend" | "shift", startDate }. Only valid on the FIRST schedule of the commitment.extendmutates only that schedule’sstartDate(move it earlier, or shorten it up to, but not past, the first sent invoice’s period start).shifttranslates every schedule and every commitment period on the whole commitment by the delta between the current first-schedulestartDateand the new one. This is blocked entirely if any non-voided invoice exists anywhere on the commitment.
commitmentInterval: new billing cadence for this schedule. The API rejects this if any period on this schedule already has a non-voided invoice, or if the schedule has 2+ steps (multi-step schedules must stay full-duration).prepaymentScheduleType: new prepay behavior. The API rejects this if any period on this schedule already has a non-voided invoice.billingTermsToAddandbillingTermsToDelete: per-step base-BT link changes (see Example 3).cascadeBillingTerms(boolean, required on every PATCH): whether linked base BTs (and their performance obligations) should move or resize along with the schedule’s date change.truemoves the BT dates with the schedule;falseleaves the schedule to mutate alone, and you own BT realignment separately.- On
extend(and on plainendDatechanges), the cascade is scoped to the target schedule’s own linked BTs. - On
shift, the cascade is commitment-wide: the API re-syncs every base BT linked to any schedule on the commitment (sync-base-bt-and-pob-dates), plus every container BT (prepaid, true-up, overage) throughsync-container-billing-terms, not just the BTs on the first schedule. This matches shift’s own semantics, since shift translates every schedule, not just the first. See Example 2.
- On
What locks a schedule down
- Any non-voided invoice on a period in this schedule blocks
commitmentIntervalchanges,prepaymentScheduleTypechanges, and shrinkingendDatebelow the latest invoiced period’s end. It does not block extendingendDateforward, or extendingstartDateearlier, as long as it doesn’t cross an invoiced period. shift-modestartDateChangeis blocked entirely if any non-voided invoice exists anywhere on the commitment (not just this schedule): shift moves every period on every schedule, so one invoiced period anywhere makes the whole operation unsafe.- Deleting a schedule requires all of the following: it’s the last (highest-sequence) schedule, it’s not the only schedule on the commitment, and none of its commitment periods have a non-voided invoice.
- Date and cadence changes must align to a billing-period boundary of every linked base BT’s cadence (when
dueIntervalUnitisn’tNONE); an unalignedendDatewould leave the last billing period partial.
When to add a new step versus a new schedule
Ask: is the date range (and cadence or prepay behavior) changing, or just the amount or BT mix within the current range?
- New schedule: the commitment is entering a new calendar phase: a new interval, a new prepay strategy, or simply the next contiguous period starting now. This is the “step-up commitment” pattern. New schedules are always appended contiguously: the new
startDatemust be exactly the day after the current last schedule’sendDate. - New step: the amount tiers within the current schedule’s existing date range, without touching dates or cadence. This is the “sequential commitment” pattern; see Add/Edit a Step.
The hard constraint that enforces this split: all steps within one schedule must share the same commitmentUnitType and resolve to the same event-type set. If you need to change either of those, adding a step won’t work; you need a new schedule.
Examples
1. Add a schedule (start a new phase)
The API checks ownership of the commitment (under that contract, for that manufacturer) before the call proceeds; mismatches return 404.
Required: startDate, endDate (both YYYY-MM-DD, endDate inclusive; endDate must be after startDate), steps[] (min 1, each with caller-supplied sequence, strictly increasing). The schedule’s own sequence is server-assigned here (unlike at commitment creation); the response returns it along with the new scheduleId and stepIds[].
Caveats:
- Contiguity:
startDatemust be exactly the day after the current last schedule’sendDate. The API enforces this structurally, not as a hand-checkable string match, so send the day immediately following. commitmentIntervalcannot be set ifsteps.length >= 2; the call returns400:"Multi-step schedules must be full-duration: omit commitmentInterval (interval/unit must be null)."- Any step with
prepaidEnabled: truerequiresprepaymentScheduleTypeto be set on the schedule; otherwise the call returns400:"prepaidEnabled steps require the schedule to carry a prepaymentScheduleType (FULL_UPFRONT / PER_COMMITMENT_PERIOD / SPLIT_ACROSS_BILLING_PERIODS)." endDatemust align to every linked BT’s billing-period boundary, or the call returns400:"Schedule endDate (X) does not align with the billing-period boundaries of billing term Y..."; ifcommitmentIntervalis set, it must also align to the schedule’s own cadence ("...does not align with the schedule's own {unit} cadence anchored at {startDate}...").- The call returns
404if the commitment isn’t found (or doesn’t belong to that contract or manufacturer).
Think of “add a schedule” as starting a new phase of this commitment. This call purely appends; existing schedules and steps stay untouched.
2. Extend or shift a schedule
Extend the end date (last schedule only):
endDateonly applies to the last schedule; any other schedule returns400:"Schedule X (sequence Y) is not the last schedule on commitment Z (max W); endDate changes are only allowed on the last schedule to preserve contiguity."- Extending forward is unbounded; shrinking is bounded below by the latest invoiced period’s end, or the call returns
400:"Cannot shrink endDate to X: schedule Y has an invoiced period ending Z; new endDate must be ≥ the latest invoiced period end." cascadeBillingTerms: trueextends or shortens the schedule’s linked base BTs (and their POBs) to match;falseleaves BTs untouched, and you own realignment.
Extend the start date earlier (first schedule only):
- This is only valid when the URL
scheduleIdis the first schedule; otherwise the call returns400:"Schedule X (sequence Y) is not the first schedule on commitment Z (min W); startDate changes are only allowed on..." - The call is blocked if it would cross a period that already has a non-voided invoice, returning
400:"Cannot extend startDate to X: schedule Y has an invoiced period starting Z; new startDate must be ≤ ..." - With
cascadeBillingTerms: false, the newstartDatemust still align with linked base BTs’ billing-period boundaries (the BT grid stays put), or the call returns400:"New startDate (X) does not align with billing-period boundaries of billing term Y..."WithcascadeBillingTerms: true, the BT grid re-anchors to the new start, so the API doesn’t enforce this alignment against the old anchor.
Shift the whole commitment’s timeline:
- This translates every schedule and every commitment period on the commitment by the delta between the old and new first-schedule
startDate. It is blocked entirely if any non-voided invoice exists anywhere on the commitment, returning400:"Cannot shift commitment X: at least one non-voided invoice has been sent. Shift translates every schedule and period by the delta, so no invoiced period can be..." cascadeBillingTermson shift is commitment-wide, not schedule-local. WithcascadeBillingTerms: true, the API re-syncs every base BT linked to any schedule on the commitment (not just the schedule in the URL) to its schedule’s new dates, throughsync-base-bt-and-pob-dates, and resizes every container BT (prepaid, true-up, overage, on both edges) plus every attached POB (on both service edges) throughsync-container-billing-termsfor the whole commitment. This is deliberate: shift moves every schedule’s dates, so a schedule-scoped cascade would leave every other schedule’s BTs behind.- With
cascadeBillingTerms: false, the API leaves BTs exactly where they are while every schedule and period on the commitment moves underneath them. You’re responsible for realigning every linked BT afterward, not just one schedule’s worth.
Composition: you can send startDateChange and endDate in the same PATCH only when the commitment has a single schedule (so “first” and “last” are the same row); in that case the API applies startDateChange first, then endDate wins as the final absolute end. commitmentInterval and prepaymentScheduleType target the URL scheduleId directly and compose freely with startDateChange on any schedule.
3. Add billing terms to steps within a schedule
This is a schedule-level operation, not a step-level one, even though it changes which BTs a specific step is linked to. The reason: all steps in a schedule must share the same event-type set. Adding a BT to one step directly could silently break that invariant for its siblings, so the schedule endpoint enforces it across every step you touch in one call, all at once.
Shape: each element of billingTermsToAdd and billingTermsToDelete is an object { [stepId]: billingTermId[] }; you can send multiple step keys per call, and they get merged. Adding a BT that’s already linked to the step is an idempotent no-op.
Caveats:
- Every
stepIdreferenced must belong to the schedule in the URL, or the call returns400:"Step X does not belong to schedule Y." - Only base BT links (
billingTermCommitmentType: null) can be added or removed this way; container (prepaid and true-up) and overage BTs are managed separately. - The same BT id cannot appear in both
billingTermsToAddandbillingTermsToDeletefor the same step in one call, or the call returns400:"Billing term(s) X appear in both billingTermsToAdd and billingTermsToDelete for step Y. Split into separate requests if you need both." - The resulting event-type set across all steps in the schedule must still match; the API returns a
400if the add/delete combination would make one step’s event types diverge from its siblings. billingTermsToAddentries must align with the schedule’sendDateand BT cadence the same way new-schedule BTs do.
Common errors and troubleshooting
Error codes referenced in this guide
200: schedule patched or deleted successfully201: schedule appended successfully400: validation error: structural (contiguity, sequencing), invoice-gate lock, alignment, or event-type-set mismatch404: commitment or schedule not found for this manufacturer or contract (includes cross-tenant mismatches, and concurrent-delete races)500: internal server error

