Enterprise SaaS / Custom Contracts
The enterprise SaaS pricing pattern is what happens when standard self-serve tiers stop fitting the deal: the customer is a global company with custom requirements, the sales cycle is months long, the contract is multi-year with negotiated terms, the deal is paid in the customer's local currency under their bank's FX preferences, and finance / legal / deal-desk all need to sign off before the deal closes. The same platform that serves $10/month self-serve customers must also serve $500k/year custom contracts without bending the platform to accommodate either side.
Real-world examples. Salesforce (custom-priced multi-year contracts with deal-desk gates), Workday, ServiceNow, SAP, Atlassian Enterprise, Snowflake, Databricks, GitHub Enterprise, GitLab Ultimate, MongoDB Atlas Enterprise. Common shape: account-specific pricing dramatically different from the catalog, multi-year terms with annual or quarterly billing, multi-currency under the customer's preferred currency, internal approval gates for any deviation from standard pricing, dedicated success manager per account, custom invoice formatting and PO requirements.
The shape of the problem
Enterprise sales mechanics translate into specific billing requirements:
- Account-specific pricing across many products. A standard plan might have 8 priced products; the enterprise contract carries custom rates on most of them, plus add-ons not on the catalog. Modeling this as a one-off plan creates plan sprawl; modeling it as negotiated prices on the deal keeps the catalog clean.
- Multi-year terms, annual billing. Contract is 3 years, but billed annually with the second/third year prices already pinned. Mid-year changes (true-ups, expansions) need handling without rebuilding the whole contract.
- Multi-currency invoicing under the customer's FX preferences. Customer wants invoices in EUR even if the catalog is USD; their finance team wants the FX rate locked at the start of each annual period for predictability.
- Deal-desk approval gates. Any deviation from standard pricing above a threshold triggers internal review - sales, deal desk, finance, legal - before the contract goes out.
- Custom invoice formatting. Enterprise customers have PO numbers, Leitweg-IDs, internal cost-center codes, custom branding requirements, and (in the EU) often need XRechnung/EN 16931 e-invoice format.
- Payment terms. Net 30/60/90 instead of "due on receipt"; customer pays via wire transfer with a manual recording flow rather than auto-debit.
- Renewal forecasting. Finance needs to know which contracts are renewing in the next quarter, what they're renewing at, and which are at risk.
Kontier blueprint
| Concern | Kontier primitive |
|---|---|
| Account-specific pricing | An agreed price stated on the deal's subscription lines; the catalog stays untouched |
| Multi-year terms with stepped prices | One phase per contract year, each with its own agreed price, and an exit commitment |
| The offer and its signature | A quote holding the subscription's draft revision; signing publishes and activates it |
| Deal-desk approval | The quote.send approval checkpoint, switched on with PUT /v1/organization/settings (approval.quote.send.*); requests are read with GET /v1/approvals; deciding them is not part of the API |
| Multi-currency under customer's preference | The subscription's currency + the FX timing setting fx_policy_invoice (period_start) |
| PO numbers, Leitweg-IDs | Customer identifiers (DE_LEITWEG_ID) and the invoice's buyer_reference, delivery_date |
| EN 16931 / XRechnung | GET /v1/invoices/{id}/xrechnung |
| Net 30/60/90 payment terms | Customer payment_terms_days |
| Manual wire transfer recording | POST /v1/invoices/{id}/record-payment with instrument and reference |
| Renewal pipeline forecasting | Analytics plus the subscription's live.auto_renew, live.current_period and live.next_cut_at (include=live), and the subscription.renewal_upcoming webhook |
Build it
IDs in the examples are placeholders: use the id each create call returns.
1. Create the enterprise customer
Code
A BUSINESS customer in an EU country needs at least one registered identifier. Each one is checked against its kind's pattern (and check digit, where one exists); GET /v1/customers/{customerId}/identifiers lists the kinds a customer may hold. payment_terms_days: 60 makes the customer's invoices due in 60 days.
2. Author the contract as a quote
The negotiated contract is a three-year deal: the platform fee at €8,400 a year in year 1, rising to €8,820 and €9,260 in years 2 and 3, plus a premium-support line at a flat €15,000 a year. Prices are agreed on the deal's lines, so the catalog stays as it is and no customer-specific plan is created.
Code
The response names the subscription_id the quote minted in DRAFT. Write its revision 1 on the subscription's own revision routes: one phase per contract year, all drawn from the annual enterprise plan (a plan with price_cadence_unit: year):
Code
How the phases read:
- Inheritance. A line absent from a later phase's
valuesinherits what an earlier phase stated, so the support line keeps its €15,000 in years 2 and 3. An agreed price is stored with the deal, so a later catalog price change does not move it. - Commitment.
exit.not_beforepoints at phase 2, the third year: a cancellation before the contract's end is refused withSUBSCRIPTION_EXIT_BLOCKED, because thegateisblock. Year 3 also records a three-monthnotice_duration, which the offer document states; the refusal itself comes fromnot_before. - Renewal.
then: "open"on the last phase means the deal runs on after year 3 (the subscription'slive.auto_renewreadstrue);"stop"would end it there instead.
To price the whole draft before you write it, send the same body to POST /v1/subscriptions/{id}/revisions/draft/preview: it answers every line's amount and each phase's totals, and saves nothing. GET /v1/quotes/{id}/revisions shows every value resolved through the inheritance ladder.
3. Put deal desk in front of the quote
There is no approval-workflow builder: Approvals ships a fixed catalog of checkpoints, and the one that covers a deal-desk commitment is quote.send, the moment a binding price leaves for the customer. Each checkpoint is three settings: approval.quote.send.enabled, approval.quote.send.threshold (the amount at or above which a quote gates; no amount gates every one) and approval.quote.send.approver_role (the role whose members decide; empty means anyone otherwise eligible). Switch it on with the settings API:
Code
The caller needs the approval.configure permission. Switching a checkpoint on is refused with 422 NO_ELIGIBLE_APPROVERS unless at least two people could decide, because nobody decides their own request; an approver role whose members cannot send quotes is refused with 422 ROLE_LACKS_PERMISSION. A threshold is compared only with quotes in its own currency: a quote in any other currency always gates. GET /v1/approvals/checkpoints shows each checkpoint with the pool of people who may decide it.
With the checkpoint on, submit the quote for review:
Code
A quote that clears the threshold parks in PENDING_APPROVAL, the call answers 202 with the approval request id, and approval.request_created fires. On approval the quote becomes APPROVED. A machine caller cannot pass a gate meant for a person: with the checkpoint enabled, an API-key caller is refused with 409 APPROVAL_REQUIRED_HUMAN rather than parked, so a signed-in person must submit the quote. approval.request_expired fires if nobody decides in time; nothing is ever approved by a deadline.
The queue is readable through the API: GET /v1/approvals?checkpoint_id=quote.send&status=PENDING lists the parked quotes, and GET /v1/approvals/{id} returns one request, the id being the request_id in the approval.* webhook payloads. Approving, rejecting and cancelling a request are done in the dashboard; there is no API for them.
Once approved, send it:
Code
When the buyer signs, the quote becomes SIGNED, and the signature publishes the subscription's revision and activates it (subscription.activated). Before a long-open offer is signed, GET /v1/quotes/{id}/signability checks that it would still go through. An offer signed on paper is recorded with POST /v1/quotes/{id}/mark-signed.
4. Lock the FX rate at the start of each period
Which rate a converted invoice reads is the setting fx_policy_invoice. Its values are invoice_issue (the default), period_start and daily_snapshot. With period_start, every document of a billing period converts at the rate in force at the period's start, so Acme's annual invoice has predictable EUR amounts even if USD/EUR moves during the year. A finalised invoice keeps its rate for ever (Exchange Rates).
Code
Saved from a live workspace, a value lands on the organization and applies to all of its workspaces (add "level": "workspace" to keep it to the one you are in); saved from a sandbox, it stays in that sandbox. GET /v1/organization/settings/resolve shows the value in force and where it came from.
For a contractual rate rather than a market one, set an override for the pair and the contract window:
Code
5. Add the PO to the first invoice
The first annual invoice is drafted when the contract starts. Before it is finalised, add the AE's PO number and the delivery date:
Code
buyer_reference and delivery_date can be changed only while the invoice is a draft. Finalising freezes the document, mints its number and pins its FX rate.
6. Customer pays by wire; record the payment manually
Code
amount is Money in major units, in the invoice's currency (EUR here); another currency is refused with 422 CURRENCY_MISMATCH. received_at is when the money moved and may not lie in the future. invoice.payment_recorded fires.
7. Export XRechnung XML for the customer's e-invoice ingestion
Code
The XML carries the buyer reference (the PO or Leitweg-ID), supplier and buyer VAT IDs and a line-by-line tax breakdown, as EN 16931 requires for B2B/B2G electronic invoicing. See E-invoicing.
Variations
- Quarterly billing. A subscription bills on its plan's cadence, and a plan's
price_cadence_unitisweek,monthoryear: there is no quarterly plan cadence. To invoice a year in four parts, use a monthly plan with the quarter's amount spread over its months, or bill the year in advance. One phase cannot mix plans of different cadences (SUBSCRIPTION_CADENCE_MISMATCH). - All-upfront prepay. Make the whole term one phase and set the line's
timingtophase_in_advance: the whole phase is billed in one invoice at its opening, priced as the sum of its periods. This applies to a recurring, unmetered line of a phase with an end date or a fixed length. - Mid-term true-up. Customer adds 50 seats mid-year. Cut the live revision at the instant (
POST /v1/subscriptions/{id}/revisions/cut), write the whole draft withPUT /v1/subscriptions/{id}/revisions/draft(the year in force now ends at the cut withthen: "next", and a phase with"continues": truecarries the newquantityto the year's end, followed by the later years as they were), and publish (POST /v1/subscriptions/{id}/revisions/draft/publish). The Subscriptions guide shows such a body in full. The cut is a billing boundary the invoice pipeline prorates across, and publishing firessubscription.revision_published. To have the true-up signed, offer it as a quote instead:POST /v1/quotes/{id}/revisionswithkind: "renegotiation"and thesubscription_idandcut_at. To schedule it, usePOST /v1/changeswithsubscription.transform(kind: set_value,attribute: quantity,from). See Scheduling. - Custom branded invoice templates. Create a document template and pick it for the customer's documents.
- Multi-entity invoicing. Acme has subsidiaries in DE, FR, IT - each needs separate invoices with their own VAT ID. Create one customer per entity, each with its own identifiers, and tag them for unified reporting.
- Renewal automation. A deal whose last phase says
then: "open"runs on (live.auto_renew: true). Readlive.current_periodandlive.next_cut_aton the subscription (GET /v1/subscriptions/{id}?include=live) to see when the next invoice is written. Thesubscription.renewal_upcomingwebhook fires when a committed term approaches its renewal date, where a statutory pre-renewal notice applies. Other lifecycle events includesubscription.activated,subscription.revision_published,subscription.phase_enteredandsubscription.cancelled.
What you don't have to build
- Per-customer pricing on top of the catalog (agreed prices on the deal, no plan sprawl)
- Stepped multi-year prices and a cancellation commitment (phases with an
exitrule) - Offer, signature and activation in one flow (quotes)
- A deal-desk gate on the moment a price leaves for the customer (the
quote.sendcheckpoint, configured withPUT /v1/organization/settings) - Period-start FX rate pinning for predictable annual invoices
- EN 16931 / XRechnung e-invoice generation
- PO and Leitweg-ID propagation onto invoices
- Net-N payment terms and dunning behavior
- Manual wire-transfer recording with audit trail
- Revenue analytics (
GET /v1/analytics/metrics,GET /v1/analytics/series) to feed renewal-pipeline reporting
Next steps
- Quotes - offers, revisions and signature
- Subscriptions - phases, lines, agreed prices and exit rules
- Approvals - the
quote.sendcheckpoint - Exchange Rates - rate pinning and overrides
- Invoices - buyer reference, XRechnung export
- Tax Compliance - reverse charge and intra-EU invoicing