Skip to content
In this section

Contracts

The agreement everything hangs off: versions instead of edits, statuses and components.

A contract is the commercial agreement every other billing object hangs off: subscriptions bill on it, credit is held against it, metered usage is priced under it and every invoice names it. It belongs to one of your customers (a sellerCustomerId from POST /customers) and bills in one currency for its whole life — credit and invoices under it are never converted.

Versions, not edits

A contract is a stable id plus an append-only history of versions. Nothing is ever updated in place: every change — a status move, a new end date, a different set of components — is a new version appended on top of the last, and every earlier version stays readable. GET /contracts shows the current version of each contract you own, with version saying how many there are.

That is what an amendment is: a compare-and-swap on the version number. You send the expectedBaseVersion you last read; if the contract has moved on since, the amendment answers 422 and you re-read before deciding again. Two integrations amending the same contract can therefore never silently overwrite each other.

Status

draft → active → suspended ⇄ active → closed. A contract starts in draft. closed is terminal: nothing about a closed contract can be amended. A transition the state machine does not allow (anything out of closed, or draft straight to suspended) answers 422.

Closing a contract does not cancel its subscriptions. The status is a record of the commercial relationship; no billing engine reads it, so a subscription on a closed contract keeps renewing and keeps issuing invoices. Cancel each one with POST /billing-api/v1/subscriptions/{subscriptionId}/cancel FIRST, then close the contract.

Endpoints

  • GET /billing-api/v1/contracts — the current version of every contract you own, keyset-paged by contract id.
  • POST /billing-api/v1/contracts — create one, in draft, for one of your customers. Idempotency-Key is required: the same key returns the contract the first call created (200 instead of 201); the same key answers 422, naming the field, when the request differs in its sellerCustomerId, currency or the lineItems themselves — compared line by line on componentType, componentConfig and quantity, order-insensitively — or in the startDate or endDate as the key first created it. It also carries an EXTRA per-seller quota of its own — 20 create requests a rolling day by default (a refused or replayed request counts too), answered as 429 with Retry-After — bounding how fast one key can open contracts, each of which allocates a number from a sequence shared across sellers.
  • POST /billing-api/v1/contracts/{contractId}/amendments — append a version: a status move, new dates, a replaced component set, or the counterparty's details as agreed. expectedBaseVersion is required and is the concurrency guard; there is no Idempotency-Key, because a repeat of a successful amendment fails the version check by construction. This one carries an EXTRA per-seller quota too — 200 amend requests a rolling day by default (a stale expectedBaseVersion counts too) — because every accepted amendment appends a version that can never be deleted.
  • GET /billing-api/v1/contracts/{contractId}/usage and GET /billing-api/v1/contracts/{contractId}/credits — what has been metered and what stored value is held under the contract.

Both write quotas, and the invoice issue quota, fail closed — while the limiter is unreachable they answer 503 with Retry-After and the write does not happen; see Errors → rate limiting.

Components

lineItems on a contract are its components: what the agreement says is being sold, each with a componentType, a quantity (decimal string) and an optional componentConfig the engine reads for that type. They are snapshotted per version — an amendment either carries the current set forward (omit lineItems) or replaces it whole (send the full new set; [] clears it). There is no partial edit, for the same reason there is no partial edit of anything else here: the version is the audit trail.

A contract may have no components at all. Its billing is then defined by the subscriptions attached to it (POST /contracts/{contractId}/subscriptions) and the metered prices set under it.

What the seller key may not do

Contracts created through this API record no dashboard user as their author (createdBy is 0): the seller key is the actor, and the audit trail names the key's seller account. Contract PDFs, tax profiles and the backoffice review of a contract stay dashboard operations.