# Anatomy of an Offer

URL: /products/offers/anatomy · Id: `page:products/offers/anatomy`

> An Offer is five decisions: a Price, how often it bills, the Entitlements it grants, what happens at a Limit, and when it is on sale.

An [Offer](/business/nouns/offer) is five decisions: a [Price](/business/nouns/price), how often it bills, the [Entitlements](/business/nouns/entitlement) it grants, what happens at a [Limit](/business/nouns/limit), and when it sells. Make them once, and the pricing page, checkout, [Portal](/business/nouns/portal) and API all follow.

## The five parts
| Part | Field | Example | Decide it by |
| --- | --- | --- | --- |
| Price | `price` | $29 | What the buyer pays today to solve the problem |
| Interval | `interval` | `month` | `once`, `day`, `week`, `month` or `year` |
| Entitlements | [`Offer.grant`](/business/nouns/offer/grant) | [Collection.export](/business/nouns/collection/export) | The [Verbs](/business/nouns/verb) the buyer may call |
| At the bound | `atBound` | `upgrade` | `stop`, `overage` or `upgrade` |
| Window | `availableFrom`, `availableUntil` | Launch week only | Unset means always on sale |

Most first Offers need three of the five: a Price, an interval and one Entitlement. Add a Limit when a customer's usage costs you money, and a window only for a launch or a seasonal deal.

## Written once
An Offer is one file in the [Startup](/business/nouns/startup)'s repository, and its frontmatter is the `Offer.create` input. Leaseline's paid plan:

```mdx title="offers/pro.mdx"
---
price: { amount: 29, currency: USD }
interval: month
atBound: upgrade
---
```

Its Entitlements are granted one Verb at a time. Pro grants the dashboard, and export up to 100 times a day:

```ts
import { $ } from 'startup-builder'

const daily = await $.Limit.create({ count: 100, period: 'day', resets: 'rolling' })
await $.Offer.grant({ offer: 'offer_pro', verb: 'Dashboard.ask' })
await $.Offer.grant({ offer: 'offer_pro', verb: 'Collection.export', limit: daily.$id })
```

Every [Product Template](/business/nouns/product-template) starts a Startup with default Offers in this shape. Here are the two a [SaaS](/products/templates/saas) starts with:

| Offer | Price | Features | Limits | At its bound | On sale |
| --- | --- | --- | --- | --- | --- |
| Starter (`starter`) | $15 per seat, billed monthly | [Dashboard.ask](/business/nouns/dashboard/ask) | [Collection.export](/business/nouns/collection/export): 10 per rolling day | `upgrade` to Pro | Always |
| Pro (`pro`) | $49 per seat, billed monthly | [Dashboard.ask](/business/nouns/dashboard/ask), [Collection.export](/business/nouns/collection/export), [SSOConnection.activate](/business/nouns/sso-connection/activate) | None. | None. | Always |

An Entitlement bounded by a Limit has `kind` `limit`; one without has `kind` `feature`. The `kind` is computed, never set.

| `atBound` | A call past the Limit |
| --- | --- |
| `upgrade` | Refused with 402 `payment-required`, naming the next Offer, which `Entitlement.check` returns as `upgrade`. |

[Offer.publish](/business/nouns/offer/publish) puts each Offer on every buy surface: `checkout`, `paymentLink`, `pricingTable`, `portalEntry`, `mcpBuyTool`, `http402`.

## Features and limits
An Entitlement with no Limit is a feature: the customer may call the Verb as often as they like. An Entitlement with a Limit is a limit: a count or an amount over a period.

A Limit resets one of two ways. A `calendar` Limit starts fresh at the top of each period, so 50,000 calls a month resets on the first. A `rolling` Limit counts the last period from now, so 10 a day means 10 in any 24 hours.

Use rolling for abuse protection, and calendar for anything a customer budgets for.

[Checking access](/products/offers/checking-access) covers how every call is checked, and the one call your own code makes.

## At the bound
`atBound` says what a call past a Limit gets. Every Offer with a Limit declares it, and no founder approval is needed to set it.

| `atBound` | The call past the Limit | Use it when |
| --- | --- | --- |
| `stop` | Refused with a 402 that names the Limit | The Limit is the promise, such as one deliverable or two revisions |
| `overage` | Allowed, and each unit past the Limit bills through the [Meter](/business/nouns/meter) | Usage is the value, as with API calls or lookups |
| `upgrade` | Refused with a 402 that names the next Offer up | A self-serve plan with a bigger plan above it |

Default to `upgrade` on self-serve plans and `overage` on usage plans. Surprise bills lose customers faster than a polite upgrade prompt.

## Where it sells
[`Offer.publish`](/business/nouns/offer/publish) puts one Offer on every buy surface at once. That is checkout on your [Domain](/business/nouns/domain), a payment link, the pricing table and the Portal. It is also a buy tool on your MCP server, and the 402 reply an agent can accept.

Stripe holds the Offer as its own [Product](/business/nouns/product), and the Price as Stripe's Price:

**Product** (`product`). The source of truth is db.sb, and Stripe holds a copy written from it.

| Property | Stripe field | Source of truth |
| --- | --- | --- |
| `price` | `default_price` | db.sb |

A new Price is a new Offer. [Raise my price](/products/offers/raise-my-price) shows how existing customers keep theirs.