The date that costs you money is not the date on the contract.
A term ending 31 December with 90 days' notice really ends on 2 October. Miss that and you have bought another year. Contract Entitlement turns an order form into machine-readable output (dated obligations, per-SKU entitlements, an exact billing schedule, commitment pace and the largest lawful renewal price) in integer minor units that always reconcile.
Runs the same engine the paid endpoint uses. Nothing is stored and no key is needed.
Press “Normalize this contract” to call the live API.
The commitments are in the contract. The contract is a PDF.
Every obligation your business has to a vendor (and every entitlement a customer has bought) is written down somewhere no system can read. So the dates get tracked by hand, in a spreadsheet, by whoever remembers.
Nobody computes the notice date
The term end is on the cover page. The notice period is in clause 11. The date that actually matters is the arithmetic between them, and nothing in your stack does that arithmetic: so it lives in someone's calendar reminder, if it lives anywhere.
Entitlement questions get answered from memory
Is this customer covered for that module? For how many seats? Did the mid-term addition take effect yet? Support asks finance, finance opens the PDF, and by then the ticket is a day old and the answer is a guess.
The spreadsheet is a cent out and nobody knows why
Prorated periods, mixed billing frequencies, mid-term additions. Each rounded on its own, in a float, in a formula copied down a column. The schedule stops matching the contract value and the reconciliation becomes an argument.
A missed date is not an incident. It is a signed obligation.
Nothing alerts. No system fails. The clause simply operates, and the first anyone hears of it is an invoice for a year nobody decided to buy.
-
Renewal
Another twelve months, agreed by nobody
The notice window closed while the renewal was still "on the list to look at". The vendor did nothing wrong; the contract said so. The cost is a full term at a price you had leverage to negotiate right up until the day you didn't.
-
Uplift
An increase accepted above what the contract permits
The renewal quote arrives, the cap is in clause 11.3, and checking it means annualising a monthly line and an annual line into one comparable figure. Faced with that under time pressure, most people accept the number on the quote.
-
Commitment
A shortfall discovered in the final month
A minimum-spend commitment is only a problem at the end, which is exactly when nothing can be done about it. Pace against it is arithmetic anyone could run monthly: and almost nobody does, because it means reopening the contract.
Five things a PDF cannot tell you.
Send the order form; get back structured output your systems can act on. No document parsing, no model guessing at clauses: you supply the terms, the engine does the arithmetic that nobody wants to do by hand.
| Output | What it answers | Why it is hard by hand |
|---|---|---|
| Obligations | Every dated commitment, soonest first, with days remaining and a status. | The notice deadline is derived, not written down. |
| Entitlements | Per-SKU quantities and the exact window each is active. | Mid-term additions and early lapses overlap. |
| Billing schedule | Every invoice date and amount across the term. | Proration, mixed frequencies, month-end drift. |
| Renewal ceiling | The largest lawful price under the uplift cap. | Monthly and annual lines must be annualised first. |
| Commitment pace | Whether spend is on track, and the projected shortfall. | Only useful monthly; only checked annually. |
Order form Validated Entitlements Schedule Obligations Into your systems
-
01
Terms in, structured
Parties, term, line items, renewal terms, commitments. Every amount is an integer number of minor units: a fractional unit price is rejected at validation rather than rounded into a dispute later.
-
02
The notice deadline, computed
Term end minus notice period, in whole UTC days. If it has passed and the contract auto-renews, the output says so in as many words rather than leaving you to infer it from a date.
-
03
A schedule that reconciles
Period boundaries come from the anchor date, so a term starting on the 31st returns to the 31st after February instead of drifting to the 28th forever. A final partial period is prorated by days, and the amounts always sum exactly to the contract value.
-
04
The renewal ceiling, annualised
A monthly line and an annual line are normalised to twelve months before the cap is applied: adding the raw unit prices reads $10,000 a month as $10,000 a year. One-time fees are excluded, because they do not recur.
-
05
Commitment pace, monthly
What should have been spent by today, what is projected at this rate, and the gap at term end. Enough to act on in month four rather than discover in month twelve.
-
06
Entitlement checks at runtime
Is this customer covered for this SKU, at this quantity, on this date? Quantities are summed across line items, so a mid-term seat addition counts. You get a reason code, not a bare boolean.
This is what an order form looks like once code can read it.
A one-year contract with 250 seats billed monthly, annual premium support, a one-time implementation fee, 90 days' notice, a 5% uplift cap and a $20,000 minimum commitment, as returned by POST /v1/contracts.
{
"contractId": "ORD-2026-0041",
"termDays": 365,
"totalContractValueMinor": 13950000,
"annualizedValueMinor": 13200000,
"renewal": {
"autoRenew": true,
"noticeDeadline": "2026-10-02T00:00:00.000Z",
"noticeWindowMissed": false,
"daysToNoticeDeadline": 32,
"renewsOn": "2026-12-31T00:00:00.000Z",
"renewalTermEndsAt": "2027-12-31T00:00:00.000Z",
"maxRenewalPriceMinor": 13860000,
"maxUpliftMinor": 660000
},
"commitment": {
"minimumSpendMinor": 2000000,
"spentToDateMinor": 400000,
"requiredToDateMinor": 1315068,
"projectedSpendMinor": 608306,
"projectedShortfallMinor": 1391694,
"onPace": false
},
"obligations": [
{ "kind": "renewal_notice", "dueAt": "2026-10-02", "daysRemaining": 32, "status": "upcoming" },
{ "kind": "auto_renewal", "dueAt": "2026-12-31", "daysRemaining": 122, "status": "upcoming" },
{ "kind": "minimum_commitment", "dueAt": "2026-12-31", "daysRemaining": 122, "status": "upcoming" },
{ "kind": "term_expiry", "dueAt": "2026-12-31", "daysRemaining": 122, "status": "upcoming" }
]
}
The schedule always sums to the total
Every entitlement's billing periods add up to its total, and those totals add up to the contract value: exactly, in integers, including prorated periods. If a schedule ever failed to reconcile, the number you invoice against would be a guess.
$138,600, not $23,100
The 5% ceiling applies to $132,000 of annualised recurring value. Summing the raw unit prices instead reads $10,000 a month as $10,000 a year and understates the lawful renewal by a factor of six. That is the arithmetic people lose renegotiations to.
You supply the terms
Contract Entitlement does not parse PDFs or infer clauses with a model. You send the terms your systems already hold, and it does the arithmetic deterministically: the same input always produces the same output.
This service is newly launched. We publish no uptime percentage, customer count, logo wall or certification claim until we have the operating history to support one. Output is arithmetic on the terms you supply; it is not legal advice.
Two calls to make a contract machine-readable
Normalize the contract
POST the terms. Back come entitlements, obligations, schedules, renewal analysis and commitment pace. One unit per contract, up to 100 per call.
Route the obligations
Every obligation has a
dueAt, adaysRemainingand a status. Pushdue_soonandpassedinto whatever already gets looked at: a queue, a CRM field, a digest.Answer entitlement questions in code
Ask whether a SKU is covered at a quantity on a date. Check 500 SKUs against one contract for one unit: charging per query would only teach you to cache the answers.
Integer money, UTC days
unitPriceMinorand every ceiling are integer minor units. A fractional price is a 400, not a rounding. Proration multiplies first and rounds once, at one named function.- Dates are whole UTC days. A notice deadline that shifts an hour across a DST boundary is a missed deadline.
endsAtis the last day covered. Contracts say "through 31 December"; treating that as exclusive silently shorts the final period.- Period boundaries come from the anchor date. 31 January clamps to 28 February and then returns to 31 March, instead of drifting to the 28th for the rest of the term.
- Output is deterministic. Same terms, same evaluation instant, same bytes: so a stored result can be diffed.
Small surface, versioned, OpenAPI-first
Authenticate with Authorization: Bearer <key>. Every error returns a stable code and a requestId you can quote, and validation failures name the exact field in details.path.
/v1/keysPublic. Emails a one-time claim token to the address you give it. No key is returned on this call, proving the mailbox is what stops anyone minting a key on your account./v1/keys/claimPublic. Spends the emailed token and returns the key, shown once: 25 contracts a month, no card./v1/contractsNormalize one contract or up to 100. Billed one unit per contract./v1/entitlements/checkUp to 500 SKU questions against one contract. Billed one unit: per contract, not per query./v1/demo/normalizePublic. Real engine, capped at 20 line items. No key required./v1/obligation-typesPublic. Every obligation kind, status and check reason code.curl -X POST https://contractentitlement-api.com/v1/contracts \ -H "Authorization: Bearer $CONTRACT_ENTITLEMENT_KEY" \ -H "content-type: application/json" \ -d '{"contract":{"contractId":"ORD-2026-0041","customer":"Acme Corp", "supplier":"Northwind Software","currency":"USD", "startsAt":"2026-01-01","endsAt":"2026-12-31", "lineItems":[{"sku":"platform.enterprise","quantity":250, "unitPriceMinor":4000,"frequency":"monthly"}], "renewal":{"autoRenew":true,"noticeDays":90, "termMonths":12,"upliftCapPct":5}}}'
Python and TypeScript SDKs Service health: https://contractentitlement-api.com/health
Priced per contract, capped so a bulk import can’t surprise you
One unit is one contract normalized: or one contract checked, however many SKUs you ask about. Every plan includes an allowance, then a per-unit overage that is hard-capped per billing period: your worst-case bill is the base price plus the cap, never more.
- 250 contracts included
- $3.00 per additional contract
- Overage capped at $1,000 per period
- Full API access, Python and TypeScript SDKs
- 1,000 contracts included
- $2.25 per additional contract
- Overage capped at $3,000 per period
- Full API access, Python and TypeScript SDKs
- 5,000 contracts included
- $1.50 per additional contract
- Overage capped at $10,000 per period
- Full API access, Python and TypeScript SDKs
- 15,000 contracts included
- $1.00 per additional contract
- Overage capped at $20,000 per period
- Full API access, Python and TypeScript SDKs
Free tier: 25 contracts per month, no card. Prices in USD, billed monthly. Payment is processed by Square through our shared billing platform; card details never touch this site.
What counts as a contract?
Does it read PDFs?
Is the contract stored?
Why integer minor units everywhere?
How is the renewal ceiling computed?
Can I evaluate it before paying?
Find the deadline before it finds you.
The free tier is 25 contracts a month, no card. Create a key, post one order form, and the response tells you the date the contract actually turns on.
One email address, no password, no card. Prefer the terminal? The same key, in two calls:
curl -X POST https://contractentitlement-api.com/v1/keys \ -H "content-type: application/json" \ -d '{"email":"you@company.com","name":"Evaluation key","source":{"source":"web","medium":"landing"}}' # 202 Accepted. A one-time token is emailed to that address — spend it, # and the response carries the key, shown once. curl -X POST https://contractentitlement-api.com/v1/keys/claim \ -H "content-type: application/json" \ -d '{"token":"<token from the email>"}'