> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.tabs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.tabs.com/_mcp/server.

# Create Commitments

If the customer has committed to a minimum spend or prepaid amount against their usage, create a commitment on the contract. Commitments are common in usage-based deals where the customer commits to a minimum spend in exchange for a discounted rate.

A commitment is made up of one or more **schedules**, and each schedule contains one or more **steps**:

* A **schedule** owns a date range, the commitment period interval, and the prepayment schedule type. Add more schedules to extend a commitment into new date ranges with different terms. Schedule date ranges must be contiguous — each `startDate` is the day after the previous schedule's `endDate`.
* A **step** owns the commitment amount, the prepaid amount, and the billing terms it measures. Add more steps within a schedule to ramp the committed amount inside a single date range.

This guide walks through creating a commitment for a contract.

## Step 1: Create the billing terms

Billing terms must exist before a commitment can reference them. A commitment doesn't carry usage-pricing logic itself. It links to one or more **billing terms (BTs)** that already define *how* usage is priced (unit or flat billing, simple, tiered, or volume pricing) and which event type is being metered. The commitment layer only adds a **target amount**, and optionally turns on prepaid, true-up, or overage behavior, on top of BTs that already exist.

For a commitment, the BTs you create must be **usage-based**: set `billingType: "UNIT"` and an `eventTypeId` so the BT measures the event stream the commitment tracks. A commitment linked to a BT with no `eventTypeId` (for example, a flat recurring fee) has nothing to measure progress against, and isn't a supported combination.

Practically, this means the workflow is always:

1. Create the BTs (this section).
2. Decide the commitment's shape: one step, multiple steps, or multiple schedules (Step 2).
3. Decide what else needs enabling: prepaid, true-up, or overage (Step 3).
4. Create the commitment, referencing the BT IDs from step 1 (Step 4).

You cannot reverse steps 1 and 4: `billingTermIds`, `billingTermsToAdd`, and `usageBillingTermId` fields are validated against existing BT records at commitment-write time, and a commitment call referencing a BT ID that doesn't exist (or belongs to a different contract or manufacturer) fails with `400`: `"Billing terms not found or deleted: <ids>."`

To create the BTs:

```javascript
POST /v3/contracts/:id/billing-terms
```

```json
{
  "billingStartDate": "2025-01-01",
  "isRecurring": true,
  "interval": "MONTH",
  "intervalFrequency": 1,
  "invoiceDateStrategy": "FIRST_OF_PERIOD",
  "netPaymentTerms": 30,
  "quantity": 1,
  "billingType": "UNIT",
  "pricingType": "SIMPLE",
  "name": "Platform Usage",
  "eventTypeId": "c5f6a7b8-9012-4abc-8def-444444444444",
  "productId": "c5f6a7b8-9012-4abc-8def-444444444444"
}
```

Required fields: `billingStartDate`, `isRecurring`, `interval`, `intervalFrequency`, `invoiceDateStrategy`, `netPaymentTerms`, `quantity`, `billingType`, `pricingType`.

Key optional fields:

* `name`: required unless `productId` is set (then pulled from the product).
* `productId`: associate a product from `GET /v3/products`; this populates `name`, `description`, `itemId`, and `classId` for any not explicitly set. Tabs recommends this for revenue-reporting accuracy.
* `eventTypeId`: the usage event type this BT meters. **Required for any BT you intend to link to a commitment**, since this is what lets the commitment's usage tracking resolve to a concrete event stream.
* `billingTermGroupId`: join an existing BTG, or omit to create a new one.

**Validation caveats:**

* `interval` enum is exactly `NONE, DAY, MONTH, HOUR, YEAR, QUARTER, SEMI_MONTH, WEEK`; anything else returns `400`: `"interval must be one of the following values: ..."`.
* `invoiceDateStrategy` enum is `FIRST_OF_PERIOD, LAST_OF_PERIOD, ARREARS, ADVANCED_DUE_START`.
* `billingType` is `UNIT` or `FLAT` only; `pricingType` is `SIMPLE`, `TIERED`, or `VOLUME`.
* For `TIERED` or `VOLUME` pricing, supply 2+ `pricing[]` entries where `tier` is 0-indexed and contiguous (0, 1, 2, and so on) and `tierMinimum` starts at 0 and strictly increases; malformed tiers return `400`.
* A successful call returns `201` with `BillingTermResponseDto` wrapped in the standard `{ payload, message, status }` envelope (`IntegratorsApiResponse`). Grab the returned billing term ID: you need it for `billingTermIds` when creating the commitment.

Repeat this call once per BT you need before creating the commitment. For example, create one per event type or product being metered, or a separate BT per usage tier if your pricing model calls for it.

## Step 2: Decide between multiple steps and multiple schedules

Before creating the commitment, decide its shape. There are two independent axes of "more than one" here, and picking the wrong one produces the wrong billing behavior.

### Multiple schedules: "step-up commitments" (independent calendar stages)

A **schedule** is a calendar-time phase of the commitment: its own `startDate` and `endDate`, its own `commitmentInterval` (billing cadence), and its own `prepaymentScheduleType`. Schedules on one commitment are ordered by `sequence`, and their **dates** must be **contiguous** (schedule N+1's `startDate` picks up exactly where schedule N's `endDate` left off, with no gaps and no overlaps). The `sequence` field itself does not have to stay gap-free over the commitment's lifetime; deleted schedules and steps can leave holes in the raw sequence values. Only the dates are a hard invariant.

Use multiple schedules when the commitment's *terms change over calendar time*: different cadence, different prepay strategy, or a different amount starting on a known future date. A classic example: year 1 is billed monthly with no prepay, and year 2 is fully prepaid upfront. This is what the user-facing docs call a **"step-up" commitment**: the customer's commitment steps up into a new phase at a known date.

### Multiple steps: "sequential commitments" (tiers within one schedule)

A **step** is a value tier *within* a single schedule's date range. Steps in a schedule are consumed in order by `sequence`; once an earlier step's capacity is exhausted, the next step's pricing (`enteredAt`) takes over, both within the *same* start and end dates and the *same* billing cadence.

Use multiple steps when the commitment's *amount tiers within one continuous period*, for example: the first 10k units this year are priced at $X, and the next 5k at $Y, with both tiers covering the same 12-month window. This is what the user-facing docs call a **"sequential" commitment**: usage sequentially fills one tier, then rolls into the next.

### The invariant that forces the choice

Steps within the same schedule **must share the same `commitmentUnitType`** and must resolve to **the same event-type set** (the set of event types implied by their linked base BTs). Changing the unit type always requires a new schedule; a step can't change it without diverging from its siblings.

Swapping in BTs that meter a different event type does **not** necessarily require a new schedule, though: you can also do it on the *existing* schedule through the schedule-level `billingTermsToAdd` and `billingTermsToDelete` PATCH (see [Add billing terms to steps within a schedule](/bill-customers/create-commitments/add-edit-schedule#3-add-billing-terms-to-steps-within-a-schedule)), as long as you add or remove the BT across every step in that schedule in the same call. That endpoint enforces the "same event-type set" invariant across all steps you touch at once, so a schedule-wide swap can stay on one schedule. A new schedule is only strictly required when you can't, or don't want to, touch every existing step's BT set: for example, when you want the old event type to keep applying to earlier steps while only new steps track the new one. See [Add/Edit a Step](/bill-customers/create-commitments/add-edit-step) for step-level mechanics.

Also note a structural rule: **multi-step schedules must be full-duration**. You cannot set `commitmentInterval` on a schedule that has 2+ steps; the API rejects it with `400`: `"Multi-step schedules must be full-duration: omit commitmentInterval (interval/unit must be null)."` Sub-interval slicing (for example, billing one tier monthly) only applies to single-step schedules.

|                                      | Multiple **schedules**                           | Multiple **steps**                  |
| ------------------------------------ | ------------------------------------------------ | ----------------------------------- |
| Colloquial name                      | "Step-up commitment"                             | "Sequential commitment"             |
| Scope of change                      | Calendar-time phase (own dates, cadence, prepay) | Value tier within one phase         |
| Can change unit type or event types? | Yes (new schedule)                               | No (must share with siblings)       |
| Ordering field                       | `sequence` on `UsageCommitmentSchedule`          | `sequence` on `UsageCommitmentStep` |
| Contiguity requirement               | Yes: no gaps or overlaps in dates                | Not applicable: same date range     |

## Step 3: Decide what else to enable: prepaid, true-up, and overage

Each step can independently turn on:

* **Prepaid** (`prepaidEnabled` + `prepaidValue`): the customer pays some or all of the commitment value upfront rather than as usage accrues. When enabled, `prepaidValue` must be a positive number (`400`: `"prepaidEnabled=true requires a positive prepaidValue."` if omitted or ≤ 0); when disabled, `prepaidValue` must be omitted or `0` (`400`: `"prepaidEnabled=false is inconsistent with prepaidValue=X."` otherwise). A prepaid step additionally requires its **schedule** to carry a `prepaymentScheduleType`; enabling prepaid on a step whose schedule has none returns `400`: `"schedule has no prepaidScheduleType set. Set the schedule's prepayment behavior (FULL_UPFRONT / PER_COMMITMENT_PERIOD / SPLIT_ACROSS_BILLING_PERIODS) before adding a prepaid step."` Decide the schedule's `prepaymentScheduleType` first if any of its steps will be prepaid.
* **True-up** (`trueUpBehavior`, commitment-level only, values `DEFAULT` or `NEVER`): what happens if the customer under-uses the commitment. `DEFAULT` bills the shortfall (trues up unmet commitment); `NEVER` forgives it silently.
* **Overage** (`overageBillingTerms` at step creation): pricing applied once usage *exceeds* the step's value. Each overage entry references an existing base usage BT through `usageBillingTermId`; the overage BT and its performance obligation are created server-side. Supply multiple entries for tiered overage on a step. **Omit the field entirely if a BT needs no overage**: sending an entry with an empty `overagePricings` array returns `400`: `"overagePricings must include at least 1 tier when this entry is present"`.
* **Price priority** (`pricePriority`, `LOW` or `HIGH`): when multiple products or BTs are linked, which one's price wins in ambiguous cases.

None of these are mutually exclusive, and all but `trueUpBehavior` are set per-step rather than per-commitment. If you're building a multi-step or multi-schedule commitment, decide prepaid and overage per step, not once for the whole thing.

## Step 4: Create the commitment

Create the commitment through the nested endpoint below, with explicit schedules and steps up front. This writes directly into the `UsageCommitmentSchedule` and `UsageCommitmentStep` tables, so the commitment is fully editable afterward through [Add/Edit a Schedule](/bill-customers/create-commitments/add-edit-schedule) and [Add/Edit a Step](/bill-customers/create-commitments/add-edit-step).

```javascript
POST /v3/contracts/:id/commitments
```

```json
{
  "commitments": [
    {
      "schedules": [
        {
          "sequence": 0,
          "startDate": "2025-01-01",
          "endDate": "2025-06-30",
          "commitmentInterval": "MONTHLY",
          "prepaymentScheduleType": "PER_COMMITMENT_PERIOD",
          "steps": [
            {
              "sequence": 0,
              "commitmentValue": 5000,
              "commitmentUnitType": "DOLLARS",
              "pricePriority": "LOW",
              "prepaidEnabled": false,
              "billingTermIds": ["550e8400-e29b-41d4-a716-446655440001"]
            }
          ]
        }
      ],
      "trueUpBehavior": "DEFAULT"
    }
  ]
}
```

Structural requirements: at least 1 commitment, each with at least 1 schedule (`"At least 1 schedule is required per commitment"`), and each schedule with at least 1 step (`"Each schedule must have at least 1 step"`). `sequence` on both schedules and steps is **caller-supplied** at creation time and must be dense and 0-indexed (0..N-1). `trueUpBehavior` is `DEFAULT` (trues up unmet commitment) or `NEVER` (forgives it): note the valid values are `DEFAULT` or `NEVER`, not `TRUE_UP` or `NEVER`.

A successful call returns `201` with `{ payload: { success: true, commitmentIds: [...] } }`.

**Caveats:**

* The call returns `400` if a referenced billing term is locked down (already invoiced) or otherwise ineligible for commitment linkage, or `404` if the contract doesn't exist for this manufacturer.
* The API treats dates you send as **inclusive**; internally it stores them exclusive (`endDate` +1 day, through `toExclusiveEndDate`). You never see the exclusive form: always send and receive inclusive dates.
* A non-prepaid step whose `commitmentUnitType` is `UNITS` can only track **one** event type across its `billingTermIds`, or the call returns `400`: `"non-prepaid UNITS steps can only have one associated event type; candidate billing terms map to N distinct event types."` Prepaid `UNITS` steps are exempt (a prepaid commitment can bundle usage across multiple event types).
* Two of a step's billing terms that share an event type must not have overlapping date ranges, since usage would double-count, or the call returns `400`: `"billing terms X and Y share event type Z and have overlapping date ranges..."`

## Common errors and troubleshooting

| Error                                                                                                 | Cause                                                                                                       | Fix                                                                                                  |
| ----------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
| `400 "Billing terms not found or deleted: <ids>"`                                                     | A `billingTermIds` entry doesn't exist, is soft-deleted, or belongs to a different contract or manufacturer | Re-fetch through `GET /v3/contracts/:id/billing-terms` and confirm the ID before reuse               |
| `400 "At least 1 billing term is required per step"`                                                  | `billingTermIds` was empty on a step                                                                        | Create the BT first (Step 1), then include its ID                                                    |
| `400 "At least 1 schedule is required per commitment"` or `"Each schedule must have at least 1 step"` | Nested-create payload missing structure                                                                     | Every commitment needs at least 1 schedule; every schedule needs at least 1 step                     |
| `400 "overagePricings must include at least 1 tier when this entry is present"`                       | Sent an `overageBillingTerms` entry with an empty pricing array                                             | Omit the overage entry entirely if there's no overage, rather than sending an empty one              |
| `400 "non-prepaid UNITS steps can only have one associated event type..."`                            | A `UNITS`, non-prepaid step's BTs map to more than one event type                                           | Split into separate steps or schedules per event type, enable prepaid, or switch to `DOLLARS`        |
| `404` on contract or commitment                                                                       | Wrong ID, or the resource belongs to a different manufacturer                                               | Ownership failures return `404`, not `403`: the API never reveals "not yours" versus "doesn't exist" |

## Error codes referenced in this guide

* `201`: created successfully (`POST /v3/contracts/:id/commitments`, `POST /v3/contracts/:id/billing-terms`)
* `400`: validation error (payload shape, BT lockdown, structural or invariant violation)
* `404`: contract not found for this manufacturer (includes cross-tenant mismatches)
* `500`: internal server error

## Get the IDs you need

| ID              | Where to get it                                                                                                                                                                                                                                       |
| --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `contractId`    | `GET /v3/contracts` (list or filter) or `GET /v3/contracts/:id`                                                                                                                                                                                       |
| `customerId`    | `GET /v3/customers` (list or filter) or `GET /v3/customers/:id`; also present on a contract's payload                                                                                                                                                 |
| `productId`     | `GET /v3/products` (list) or `GET /v3/products/:id`: optional, used to prefill BT fields                                                                                                                                                              |
| `billingTermId` | The response of `POST /v3/contracts/:id/billing-terms` (Step 1), or `GET /v3/contracts/:id/billing-terms` and `GET /v3/billing-terms` (filterable by `contractId`, `customerId`, `productId`, `billingTermGroupId`, date range) to list existing ones |
| `eventTypeId`   | There is no `v3` integrators-api endpoint to list event types; obtain it from whatever internal process defines your usage event types (for example, an existing BT's `eventTypeId` field, or the events-microservice's own event-type records)       |
| `commitmentId`  | The response of `POST /v3/contracts/:id/commitments` (`commitmentIds[]`), or `GET /v3/commitments` (list, filterable by `customerId` and `contractId`)                                                                                                |

## Next steps

* [Add/Edit a Schedule](/bill-customers/create-commitments/add-edit-schedule)
* [Add/Edit a Step](/bill-customers/create-commitments/add-edit-step)